手把手FFmpeg入门——视频解码+解封装
环境:
QT5.7 64位
目的:
将视频解码为PCM和PPM文件
基础:
有点杂,几乎不需要基础,能看英文文档就行
基本原理:
1.无非是:解协议->解封装->解码,
这里没有协议层.
封装即各种文件格式,编码即文件内数据的存储格式
不到150行的代码:
#include <QDebug> extern "C" { #include <libavutil/imgutils.h> #include <libavutil/samplefmt.h> #include <libavutil/timestamp.h> #include <libavformat/avformat.h> } struct Img { int w, h; int pix_fmt; uint8_t *data[4]; /*假装是4字节对齐*/ int linesize[4]; int bufsize; }; struct Demuxe { AVFormatContext* fmtctx; AVCodecContext* codec_ctx; AVCodec* codec; AVStream* st; int st_idx; char *filename; FILE *fp; }; static Img img; static Demuxe audio; static Demuxe video; static Demuxe src; int refcounted = 0; int decode(AVPacket *pkt, AVFrame *frame) { int ret, got_frame, decoded = pkt->size; if(pkt->stream_index == video.st_idx){ ret = avcodec_decode_video2(video.codec_ctx, frame, &got_frame, pkt); if(ret<0) exit(1); if(got_frame){ av_image_copy(img.data, img.linesize, (const uint8_t**)frame->data, frame->linesize, (AVPixelFormat)frame->format, frame->width, frame->height); fwrite(img.data[0], img.bufsize, 1, video.fp); } }else if(pkt->stream_index == audio.st_idx){ ret = avcodec_decode_audio4(audio.codec_ctx, frame, &got_frame, pkt); decoded = FFMIN(pkt->size, ret); int size = frame->nb_samples*av_get_bytes_per_sample((AVSampleFormat)frame->format); fwrite(frame->extended_data[0], size, 1, audio.fp); } if(got_frame && refcounted) av_frame_unref(frame); return decoded;/*返回解码的字节数*/ } int construct_context(int media_type, Demuxe *dst) { dst->st_idx = av_find_best_stream(src.fmtctx, (AVMediaType)media_type, -1, -1, NULL, 0); dst->st = src.fmtctx->streams[dst->st_idx]; dst->codec = avcodec_find_decoder(dst->st->codecpar->codec_id); dst->codec_ctx = avcodec_alloc_context3(dst->codec); avcodec_parameters_to_context(dst->codec_ctx, dst->st->codecpar); AVDictionary *dict; av_dict_set(&dict, "refcounted", refcounted?"1":"0", 0); return avcodec_open2(dst->codec_ctx, dst->codec, &dict); } int main() { int ret =-1; src.filename = "D:/fmt_mkv.mkv"; audio.filename = "D:/audiomp4.pcm"; video.filename = "D:/videomp4.ppm"; src.fmtctx = avformat_alloc_context(); avformat_open_input(&src.fmtctx, src.filename, NULL, NULL); avformat_find_stream_info(src.fmtctx, NULL); if(!construct_context(AVMEDIA_TYPE_VIDEO, &video)){ video.fp = fopen(video.filename, "wb+"); img.w = video.codec_ctx->width; img.h = video.codec_ctx->height; img.pix_fmt = video.codec_ctx->pix_fmt; ret = av_image_alloc(img.data, img.linesize, img.w, img.h, (AVPixelFormat)img.pix_fmt, 1); if(ret < 0) goto end; else img.bufsize = ret; } if(!construct_context(AVMEDIA_TYPE_AUDIO, &audio)){ audio.fp = fopen(audio.filename, "wb+"); } // 解码 AVFrame *frame = av_frame_alloc(); AVPacket *pkt = av_packet_alloc(); qDebug() << "pkt.data=" << pkt->data << ", pkt.size=" << pkt->size; while(av_read_frame(src.fmtctx, pkt)>=0){ decode(pkt, frame); } // 根据dump的格式就可以用ffplay查看数据 av_dump_format(src.fmtctx, 0, src.filename, 0); end: avformat_close_input(&src.fmtctx); avcodec_free_context(&audio.codec_ctx); avcodec_free_context(&video.codec_ctx); if(audio.fp) fclose(audio.fp); if(video.fp) fclose(video.fp); av_frame_free(&frame); av_free(img.data[0]); }
注意:
1. 对于音频只能读取第一个通道的数据
2. 当启用帧的引用计数时,将拷贝decoder的缓冲区内容到frame的ref buf中,所以不用了以后一定要记得用av_frame_unref取引用
3. 文档上说必须要av_image_copy视频帧数据,因为raw格式是非字节对齐的。
因此这里猜想在ffmpeg的image里存储方式是字节对齐的,linesize和实际大小不一样。
切勿盲目手动操作img数据
4. 使用ffplay播放视频命令如下:
ffplay -f rawvideo -pix_fmt yuv420p -video_size 1280x720 D:/videomp4.ppm
ffplay -f 视频格式 -pix_fmt 像素格式 -video_size 宽x高 路径
播放音频命令:
ffplay -f f32le -ac 1 -ar 48000 D:/audiomp4.pcm
ffplay -f 音频格式 -ac 通道数 -ar 采样率 路径
就上面这种格式的电影《东方不败》
Input #0, matroska,webm, from 'D:/fmt_mkv.mkv':
Metadata:
encoder : libebml v1.2.3 + libmatroska v1.3.0
creation_time : 2017-05-06T06:43:21.000000Z
Duration: 01:47:36.32, start: 0.000000, bitrate: 1967 kb/s
Stream #0:0: Video: h264 (High), yuv420p(progressive), 1280x720 [SAR 1:1 DAR 16:9], 24 fps, 24 tbr, 1k tbn, 48 tbc (default)
Stream #0:1: Audio: aac (LC), 48000 Hz, stereo, fltp (default)
Stream #0:2: Audio: aac (LC), 48000 Hz, stereo, fltp
解码以后的纯数据格式视频(166GB),音频(单通道990MB),
而h264编码下mkv文件的总大小只有1.47GB,压缩率10倍以上,可以说很nice
但解码时间也长,大约用时20min