一 音视频解码流程
1.音频解码
相关头文件
#include <libavutil/frame.h>
#include <libavutil/mem.h>
#include <libavcodec/avcodec.h>
解码流程
1. 初始化AVPacket 结构体
AVPacket *av_packet_alloc(void); 注:老的接口在开始解码钱需要调用avcodec_resgister_all 注册编码器
2.根据ID查找对应的编码器
AVCodec *avcodec_find_decoder(enum AVCodecID id);
3.使用编码器ID舒适化数据流解析上下文结构体AVCodecParserContext
该结构体可用于从连续的数据流中分理处一帧帧为解码的数据,若要解码的对象无需此操作,可跳过
AVCodecParserContext *av_parser_init(int codec_id);
4.为编码器分配空间,打开编码器
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
5.为数据帧结构体分配空间
AVFrame *av_frame_alloc(void);
6.打开输入输出
7.从数据流中取出一个AVPacket
此处填入存放数据流的指针,取出一个packet后,下次在该数据流指针的基础上加上偏移----此次取出的数据大小,即函数返回值,传入的数据流大小也要减去该偏移,当剩余数据流大小小于预期(4096)时,及时从输入读取数据,并将本次数据置于新读取的数据前端。
int av_parser_parse2(AVCodecParserContext *s, AVCodecContext *avctx, uint8_t **poutbuf, int *poutbuf_size, const uint8_t *buf, int buf_size, int64_t pts, int64_t dts, int64_t pos);
8.将数据包送入解码器
packet得data要自己分配空间
int avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
9.接收解码后的数据
解码后的数据使用while循环接收,直至所有数据被接收完成
int avcodec_receive_frame(AVCodecContext *avctx, AVFrame *frame);
10.获取本次采样数据的大小
int av_get_bytes_per_sample(enum AVSampleFormat sample_fmt)
11.处理解码后的数据
7-11重复执行,只要所有数据被解码完毕
12.冲刷解码缓冲区
执行8-11,但是不传入数据
13.释放资源
关闭输入/输出,释放5 4 3 1中申请的资源,注意逆序释放
av_frame_free(&decoded_frame);
avcodec_free_context(&c);
av_parser_close(parser);
av_packet_free(&pkt);
2.视频解码
流程同音频解码
二 音视频编码流程
1.音频编码
相关头文件
#include <libavcodec/avcodec.h>
#include <libavutil/channel_layout.h>
#include <libavutil/common.h>
#include <libavutil/frame.h>
#include <libavutil/samplefmt.h>
编码流程
1.查找编码器
AVCodec *avcodec_find_encoder(enum AVCodecID id);
2. 为编码器上下文分配空间
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
3.设置编码器参数
c->bit_rate = 64000;
c->sample_fmt = AV_SAMPLE_FMT_S16;
4.检查当前格式是否被编码器支持
使用下列函数检查当前格式是否被编码器支持
static int check_sample_fmt(const AVCodec *codec, enum AVSampleFormat sample_fmt)
{
const enum AVSampleFormat *p = codec->sample_fmts;
while (*p != AV_SAMPLE_FMT_NONE) {
if (*p == sample_fmt)
return 1;
p++;
}
return 0;
}
5.设置音频解码器参数
实际使用过程中, 均指定为真实参数
设置合适的频率,示例给出的是在所支持的频率中找出最接近44100的一个,
c->sample_rate = select_sample_rate(codec);
static int select_sample_rate(const AVCodec *codec)
{
const int *p;
int best_samplerate = 0;
if (!codec->supported_samplerates)
return 44100;
p = codec->supported_samplerates;
while (*p) {
if (!best_samplerate || abs(44100 - *p) < abs(44100 - best_samplerate))
best_samplerate = *p;
p++;
}
return best_samplerate;
}
设置通道布局
c->channel_layout = select_channel_layout(codec);
//根据音频布局选择通道
c->channels = av_get_channel_layout_nb_channels(c->channel_layout);
static int select_channel_layout(const AVCodec *codec)
{
const uint64_t *p;
uint64_t best_ch_layout = 0;
int best_nb_channels = 0;
if (!codec->channel_layouts)
return AV_CH_LAYOUT_STEREO;
p = codec->channel_layouts;
while (*p) {
int nb_channels = av_get_channel_layout_nb_channels(*p);
if (nb_channels > best_nb_channels) {
best_ch_layout = *p;
best_nb_channels = nb_channels;
}
p++;
}
return best_ch_layout;
}
6. 打开编码器
7. 初始化AVPacket 结构体
AVPacket *av_packet_alloc(void);
8.为AVFrame分配空间
AVFrame *av_frame_alloc(void);
9.设置frame参数
frame->nb_samples = c->frame_size;
frame->format = c->sample_fmt;
frame->channel_layout = c->channel_layout;
10. 为视频音频数据分配新的缓冲区
一定要在8之后执行该步骤,第二个参数为字节对齐,官方例程给的为32
int av_frame_get_buffer(AVFrame *frame, int align);
11. 确保frame可写
当不可写时,会为该frame重新分配空间并拷贝
int av_frame_make_writable(AVFrame *frame);
12.发送数据帧
填充frame->data,其他参数保持之前的不变
int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
13.接收编码后的数据帧
int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
接收完数据后释放当前帧得数据,但保持其他参数不变
void av_packet_unref(AVPacket *pkt);
14.处理编码后得数据
重复执行11倒14,直至所有数据被处理完毕
15.冲刷缓冲区
不发送数据帧 步骤11 ,直接读取编码帧 步骤12
16.释放申请过得资源
逆序释放申请过得资源
avcodec_free_context(&c);
av_frame_free(&frame);
av_packet_free(&pkt);
2.视频编码
相关头文件
#include <libavcodec/avcodec.h>
#include <libavutil/opt.h>
#include <libavutil/imgutils.h>
编码流程
1. 查找编码器
可以通过名字或者编码器ID来查找编码器
AVCodec *avcodec_find_encoder_by_name(const char *name);
2.为编码器分配上下文存储空间
AVCodecContext *avcodec_alloc_context3(const AVCodec *codec);
3.初始化AVPacket 结构体
AVPacket *av_packet_alloc(void);
4. 设置编码器参数
c->bit_rate = 400000;
c->width = 352;
c->height = 288;
c->time_base = (AVRational){1, 25};
c->framerate = (AVRational){25, 1};
c->gop_size = 10;
c->max_b_frames = 1;
c->pix_fmt = AV_PIX_FMT_YUV420P;
// h264 码流可以设置预设参数
if (codec->id == AV_CODEC_ID_H264)
av_opt_set(c->priv_data, "preset", "slow", 0);
5. 打开编码器
int avcodec_open2(AVCodecContext *avctx, const AVCodec *codec, AVDictionary **options);
6. 为AVFrame分配空间
AVFrame *av_frame_alloc(void);
7. 设置frame参数
frame->format = c->pix_fmt;
frame->width = c->width;
frame->height = c->height;
8. 为视频音频数据分配新的缓冲区
一定要在7之后执行该步骤,第二个参数为字节对齐,官方例程给的为32
int av_frame_get_buffer(AVFrame *frame, int align);
9. 确保frame可写
当不可写是,会为该frame重新分配空间并拷贝
int av_frame_make_writable(AVFrame *frame);
10.设置pts
frame->pts = i;
11. 向编码器发送数据帧
填充frame->data; 其他参数保持前面步骤的填写
int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
12. 接收编码后的数据帧
int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);
接收完数据后释放当前帧得数据,但保持其他参数不变
void av_packet_unref(AVPacket *pkt); 此接口在当前版本中无需调用,在avcodec_receive_packet接口中会调用该接口
13. 处理编码后得数据
重复执行10倒13,直至所有数据被处理完毕
14. 冲刷缓冲区
不发送数据帧----步骤11 ,直接读取编码帧----步骤12
15. 释放申请过得资源
逆序释放申请过得资源
avcodec_free_context(&c);
av_frame_free(&frame);
av_packet_free(&pkt);
四 音视频解封装
相关头文件
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
封装流程
1. 根据具输出文件格式申请AVFormatContext
int avformat_alloc_output_context2(AVFormatContext **ctx, ff_const59 AVOutputFormat *oformat, const char *format_name, const char *filename);
后面三个成员根据实际情况填一个就行
2. 添加音视频流,即为音视频流申请AVstream
第二个参数为编码器参数,如果编码器确定,可将该参数设为NULL,然后自行添加i编码器参数
AVStream *avformat_new_stream(AVFormatContext *s, const AVCodec *c);
s->nb_streams-1 为该流的ID即返回结构体得AVStream的成员Id的值
通常情况下c填NULL,手动对AVStream 结构体中的codecpar进行赋值,
或从codecContex中复制
3. 设置该音视频流得参数
设置音视频流得参数时有两种方式,
1)从编码器拷贝
int avcodec_parameters_from_context(AVCodecParameters *par, const AVCodecContext *codec);
2)手动赋值
音频:
Stream->codecpar->codec_id = eAudioCodecID;
Stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
Stream->codecpar->sample_rate = pAudioParam->nSampleRate;
Stream->codecpar->channels = pAudioParam->nChannelNum;
Stream->codecpar->channel_layout = AV_CH_LAYOUT_MONO; //双声道用AV_CH_LAYOUT_STEREO
Stream->codecpar->frame_size = AUDIO_ENCODE_FRAME_SIZE;
Stream->time_base.den = pAudioParam->nSampleRate;
Stream->time_base.num = 1;
//aac 需要在extradata中加入音频参数的头
char chAudioConfig[2];
char nRateIndex = 0;
char nAudioAACType = FF_AAC_PROFILE_ID;
for (nRateIndex = 0; nRateIndex < 16; nRateIndex++)
if (g_AudioSampleRates[nRateIndex] == pAudioParam->nSampleRate)
break;
if (nRateIndex == 16) {
ERROR_LOG("nRateIndex = %d \n",nRateIndex);
}
chAudioConfig[0] = (nAudioAACType<<3) | (nRateIndex>>1);
chAudioConfig[1] = (nRateIndex<<7) | (pAudioParam->nChannelNum<<3);
pStAudioStream->codecpar->extradata = (uint8_t *)av_mallocz(2);
memcpy(pStAudioStream->codecpar->extradata, chAudioConfig, 2);
pStAudioStream->codecpar->extradata_size= 2;
视频:
此处注意提取sps和pps的方法
stream->codecpar->codec_id = AV_CODEC_ID_H264;
stream->codecpar->width = pVideoParam->nWidth;
stream->codecpar->height = pVideoParam->nHeight;
stream->codecpar->codec_type = AVMEDIA_TYPE_VIDEO;
stream->codecpar->bit_rate = pVideoParam->nKBps*1024;
stream->time_base.den = VIDEO_TIME_BASE_AV;
stream->time_base.num = 1;
stream->index = VIDEO_STREAM_INDEX; //可以由2中分配 待确认 与 AVFormatContext中得对应
//同时在视频帧中提取sps和pps信息,存入extradata中
streams->codecpar->extradata = (uint8_t *)av_mallocz(nSpsPpsLen);
memcpy(streams->codecpar->extradata, pFrameData, nSpsPpsLen);
streams->codecpar->extradata_size = nSpsPpsLen;
4. 打开输出文件
int avio_open(AVIOContext **s, const char *url, int flags);
s: 填写AVFormatContext中得oc->pb
url: 输出文件得名字
flags: 写AVIO_FLAG_WRITE,读AVIO_FLAG_READ,读写:AVIO_FLAG_READ_WRITE
5.写文件头
int avformat_write_header(AVFormatContext *s, AVDictionary **options);
6.交叉写入音视频帧
比较音视频时间戳的时间(由调用者控制交叉写入时需要该接口):
int av_compare_ts(int64_t ts_a, AVRational tb_a, int64_t ts_b, AVRational tb_b);
根据需要转换时基
void av_packet_rescale_ts(AVPacket *pkt, AVRational tb_src, AVRational tb_dst);
pkt->stream_index = st->index; // 设置当前要写入pkt的流的index,决定了当前是音频还是视频
写入数据帧(调用该接口由ffmepg负责影视频得交叉写入,av_write_frame由写入者控制)
int av_interleaved_write_frame(AVFormatContext *s, AVPacket *pkt);
7. 写入文件尾
int av_write_trailer(AVFormatContext *s);
8.关闭当前打开得资源
if (have_video)
close_stream(oc, &video_st);
if (have_audio)
close_stream(oc, &audio_st);
if (!(fmt->flags & AVFMT_NOFILE))
/* Close the output file. */
avio_closep(&oc->pb);
/* free the stream */
avformat_free_context(oc);
四 音视频解封装
相关头文件
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>
解封装流程
1.打开输入输入
int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);
2.查找输入中得流得信息
int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);
3.读取数据流
数据流读取厚更具pkt->stream_index来判断当前读取得数据包类型
int av_read_frame(AVFormatContext *s, AVPacket *pkt);
4.处理读取出来得数据
5.关闭已打开得资源
转封装注意事项
转封装时注意原文件中可能存在多个视频流,注意根据原视频流中得stream->codecpar->codec_type进行合理剔除