環境:
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
