手把手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

 

posted @ 2018-07-25 17:05  蛮三  阅读(2562)  评论(0编辑  收藏  举报