音视频编码流程简介(使用 ffmpeg)
第一步:注册组件(编码器、解码器…)
第二步:初始化封装格式上下文
第三步:打开输入文件
第四步:创建输出码流(设置为视频流或音频流)
第五步:查找视频/音频编码器
第六步:打开视频/音频编码器
第七步:写入文件头信息(有些文件头信息)->一般情况下都会有
第八步:循环编码视频像素数据->视频压缩数据
第九步:将编码后的视频/音频压缩数据写入文件中
第十步:输入像素/采样数据读取完毕后回调函数
在视频解码前,先了解以下几个基本的概念:
编解码器(CODEC):能够进行视频和音频压缩(CO)与解压缩(DEC),是视频编解码的核心部分。
容器/多媒体文件(Container/File):没有了解视频的编解码之前,总是错误的认为平常下载的电影的文件的后缀(avi,mkv,rmvb等)就是视频的编码方式。
事实上,刚才提到的几种文件的后缀并不是视频的编码方式,只是其封装的方式。一个视频文件通常有视频数据、音频数据以及字幕等,封装的格式决定这些数据在文件中是如何的存放的,封装在一起音频、视频等数据组成的多媒体文件,也可以叫做容器(其中包含了视音频数据)。
所以,只看多媒体文件的后缀名是难以知道视音频的编码方式的。
流数据 Stream,例如视频流(Video Stream),音频流(Audio Stream)。流中的数据元素被称为帧Frame。
avcodec_find_decoder:根据指定的AVCodecID查找注册的解码器。
av_parser_init:初始化AVCodecParserContext。
avcodec_alloc_context3:为AVCodecContext分配内存。
avcodec_open2:打开解码器。
av_parser_parse2:解析获得一个Packet。
avcodec_send_packet:将AVPacket压缩数据给解码器。
avcodec_receive_frame:获取到解码后的AVFrame数据。
av_get_bytes_per_sample: 获取每个sample中的字节数。
AVCodecParser:用于解析输入的数据流并把它分成一帧一帧的压缩编码数据。
比较形象的说法就是把长长的一段连续的数据“切割”成一段段的数据。
1. 注册
只需要调用一次,故一般放在main函数中。也可以注册某个特定的容器格式,但通常来说不需要这么做。
2. 打开文件
avformat_open_input该函数读取文件的头信息,并将其信息保存到AVFormatContext结构体中。
3. 获取必要的CODEC参数
够获取到足够多的关于文件、流和CODEC的信息,并将这些信息填充到AVFormatContext结构体中。
但任何一种多媒体格式(容器)提供的信息都是有限的,而且不同的多媒体制作软件对头信息的设置也不尽相同,在制作多媒体文件的时候难免会引入一些错误。也就是说,仅仅通过avformat_open_input并不能保证能够获取所需要的信息,所以一般要使用
avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options)
avformat_find_stream_info主要用来获取必要的CODEC参数,设置到ic->streams[i]->codec。
4. 打开解码器
经过上面的步骤,已经将文件格式信息读取到了AVFormatContext中,要打开流数据相应的CODEC需要经过下面几个步骤
找到视频流 video stream
一个多媒体文件包含有多个原始流,例如 movie.mkv这个多媒体文件可能包含下面的流数据
原始流 1 h.264 video
原始流 2 aac audio for Chinese
原始流 3 aac audio for English
原始流 4 Chinese Subtitle
原始流 5 English Subtitle
5. 读取数据帧并解码
已经有了相应的解码器,下面的工作就是将数据从流中读出,并解码为没有压缩的原始数据
AVPacket packet; while (av_read_frame(pFormatCtx, &packet) >= 0) { if (packet.stream_index == videoStream) { int frameFinished = 0; avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet); if (frameFinished) { doSomething(); } } }
上述代码调用av_read_frame将数据从流中读取数据到packet中,并调用avcodec_decode_video2对读取的数据进行解码。
6. 关闭
需要关闭avformat_open_input打开的输入流,avcodec_open2打开的CODEC
avcodec_close(pCodecCtxOrg);
avformat_close_input(&pFormatCtx);
#include#include #include #include #ifdef __cplusplus extern "C" { #endif #include #include #ifdef __cplusplus }; #endif int openCodecContext(const AVFormatContext *pFormatCtx, int *pStreamIndex, enum AVMediaType type, AVCodecContext **ppCodecCtx) { int streamIdx = -1; // 获取流下标 for (int i = 0; i < pFormatCtx->nb_streams; i++) { if (pFormatCtx->streams[i]->codec->codec_type == type) { streamIdx = i; break; } } if (streamIdx == -1) { printf("find video stream failed!\n"); exit(-2); } // 寻找解码器 AVCodecContext *pCodecCtx = pFormatCtx->streams[streamIdx]->codec; AVCodec *pCodec = avcodec_find_decoder(pCodecCtx->codec_id); if (NULL == pCodec) { printf("avcode find decoder failed!\n"); exit(-2); } //打开解码器 if (avcodec_open2(pCodecCtx, pCodec, NULL) < 0) { printf("avcode open failed!\n"); exit(-2); } *ppCodecCtx = pCodecCtx; *pStreamIndex = streamIdx; return 0; } //static const char *media_file = "ande_302.mp4"; int main(int argc, char* argv[]) { QCoreApplication a(argc, argv); AVFormatContext *pInFormatCtx = nullptr; AVCodecContext *pVideoCodecCtx = nullptr; AVCodecContext *pAudioCodecCtx = nullptr; AVPacket *pPacket = nullptr; AVFrame *pFrame = nullptr; int ret; /* 支持本地文件和网络url */ const char streamUrl[] = "ande_flv.flv"; printf("-------hello,ffmpeg!--------\n"); /* 1. 注册 */ av_register_all(); pInFormatCtx = avformat_alloc_context(); /* 2. 打开流 */ if(avformat_open_input(&pInFormatCtx, streamUrl, NULL, NULL) != 0) { printf("Couldn't open input stream.\n"); return -1; } /* 3. 获取流的信息 */ if(avformat_find_stream_info(pInFormatCtx, NULL) < 0) { printf("Couldn't find stream information.\n"); return -1; } int videoStreamIdx = -1; int audioStreamIdx = -1; /* 4. 寻找并打开解码器 */ openCodecContext(pInFormatCtx, &videoStreamIdx, AVMEDIA_TYPE_VIDEO, &pVideoCodecCtx); openCodecContext(pInFormatCtx, &audioStreamIdx, AVMEDIA_TYPE_AUDIO, &pAudioCodecCtx); pPacket = av_packet_alloc(); pFrame = av_frame_alloc(); int cnt = 10; while (cnt--) { /* 5. 读流数据, 未解码的数据存放于pPacket */ ret = av_read_frame(pInFormatCtx, pPacket); if (ret < 0) { printf("av_read_frame error\n"); break; } /* 6. 解码, 解码后的数据存放于pFrame */ /* 视频解码 */ if (pPacket->stream_index == videoStreamIdx) { avcodec_decode_video2(pVideoCodecCtx, pFrame, &ret, pPacket); if (ret == 0) { printf("video decodec error!\n"); continue; } printf("* * * * * * video * * * * * * * * *\n"); printf("___height: [%d]\n", pFrame->height); printf("____width: [%d]\n", pFrame->width); printf("pict_type: [%d]\n", pFrame->pict_type); printf("___format: [%d]\n", pFrame->format); ///AVSampleFormat printf("* * * * * * * * * * * * * * * * * * *\n\n"); } /* 音频解码 */ if (pPacket->stream_index == audioStreamIdx) { avcodec_decode_audio4(pAudioCodecCtx, pFrame, &ret, pPacket); if (ret < 0) { printf("audio decodec error!\n"); continue; } printf("* * * * * * audio * * * * * * * * * *\n"); printf("____nb_samples: [%d]\n", pFrame->nb_samples); printf("__samples_rate: [%d]\n", pFrame->sample_rate); printf("channel_layout: [%lu]\n", pFrame->channel_layout); printf("________format: [%d]\n", pFrame->format); printf("* * * * * * * * * * * * * * * * * * *\n\n"); } av_packet_unref(pPacket); } av_frame_free(&pFrame); av_packet_free(&pPacket); avcodec_close(pVideoCodecCtx); avcodec_close(pAudioCodecCtx); avformat_close_input(&pInFormatCtx); return a.exec(); }