FFmpeg視頻解碼尾部掉幀

《音視頻文章匯總》
接觸ffmpeg有一段時間了亭引,現(xiàn)遇到一個視頻解碼尾部掉幀的問題回挽,記錄下庶弃,緩沖區(qū)已經(jīng)刷過儒老,但還是尾部少幀,雖然尾部少一幀兩幀對視頻整體效果沒啥影響(一般視頻疫苗播放25幀),但學(xué)東西就學(xué)透吧

FFmpeg從MP4文件中抽取yuv純視頻文件有兩種方式

1.命令行工具ffmpeg直接抽取yuv文件

ffmpeg -i in.mp4 out_commandline.yuv

in.mp4文件是一個768x432,yuv420p,23fps的視頻文件四啰,抽取出來的yuv文件的總大小為115,955,712字節(jié),可以計(jì)算出總幀數(shù)115955712/(7684321.5) = 233幀數(shù)

image

image

通過代碼解碼mp4文件后,看生成的yuv文件是否大小和上述一直

2.通過代碼解封裝demux粗恢,分別獲取音頻pcm和視頻yuv文件,有兩個版本的代碼

  • 3.1以前的音視頻編解碼分別用
    解碼API
    avcodec_decode_video2()
    avcodec_decode_audio4():
    編碼API
    avcodec_encode_video2()
    avcodec_encode_audio2()
  • 3.1之后的音視頻編碼
    解碼API
    avcodec_send_packet()
    avcodec_receive_frame()
    編碼API
    avcodec_send_frame()
    avcodec_receive_packet()

I.3.1版本之前的解封裝demux代碼官方示例程序為如下柑晒,解碼自己的MP4文件只需更改文件路徑

將官方代碼抽取為C++一個類的類方法,傳入本地in.mp4路徑進(jìn)行解碼


image

傳入本地路徑


圖片.png

image
FFmpegs::FFmpegs()
{

}
static int decode_packet(int *got_frame, int cached)
{
    int ret = 0;
        if (pkt.stream_index == video_stream_idx) {
            /* decode video frame */
            ret = avcodec_decode_video2(video_dec_ctx, frame, got_frame, &pkt);
            if (ret < 0) {
                fprintf(stderr, "Error decoding video frame\n");
                return ret;
            }
            if (*got_frame) {
                printf("video_frame%s n:%d coded_n:%d pts:%s\n",
                       cached ? "(cached)" : "",
                       video_frame_count++, frame->coded_picture_number,
                       av_ts2timestr(frame->pts, &video_dec_ctx->time_base));
                /* copy decoded frame to destination buffer:
                 * this is required since rawvideo expects non aligned data */
                av_image_copy(video_dst_data, video_dst_linesize,
                              (const uint8_t **)(frame->data), frame->linesize,
                              video_dec_ctx->pix_fmt, video_dec_ctx->width, video_dec_ctx->height);
                /* write to rawvideo file */
                fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);
            }
        } else if (pkt.stream_index == audio_stream_idx) {
            /* decode audio frame */
            ret = avcodec_decode_audio4(audio_dec_ctx, frame, got_frame, &pkt);
            if (ret < 0) {
                fprintf(stderr, "Error decoding audio frame\n");
                return ret;
            }
            if (*got_frame) {
                printf("audio_frame%s n:%d nb_samples:%d pts:%s\n",
                       cached ? "(cached)" : "",
                       audio_frame_count++, frame->nb_samples,
                       av_ts2timestr(frame->pts, &audio_dec_ctx->time_base));
                ret = av_samples_alloc(audio_dst_data, &audio_dst_linesize, av_frame_get_channels(frame),
                                       frame->nb_samples, (AVSampleFormat)frame->format, 1);
                if (ret < 0) {
                    fprintf(stderr, "Could not allocate audio buffer\n");
                    return AVERROR(ENOMEM);
                }
                /* TODO: extend return code of the av_samples_* functions so that this call is not needed */
                audio_dst_bufsize =
                    av_samples_get_buffer_size(NULL, av_frame_get_channels(frame),
                                               frame->nb_samples, (AVSampleFormat)frame->format, 1);
                /* copy audio data to destination buffer:
                 * this is required since rawaudio expects non aligned data */
                av_samples_copy(audio_dst_data, frame->data, 0, 0,
                                frame->nb_samples, av_frame_get_channels(frame), (AVSampleFormat)frame->format);
                /* write to rawaudio file */
                fwrite(audio_dst_data[0], 1, audio_dst_bufsize, audio_dst_file);
                av_freep(&audio_dst_data[0]);
            }
        }
        return ret;
}
static int open_codec_context(int *stream_idx,
                              AVFormatContext *fmt_ctx, enum AVMediaType type)
{
    int ret;
    AVStream *st;
    AVCodecContext *dec_ctx = NULL;
    AVCodec *dec = NULL;
    ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not find %s stream in input file '%s'\n",
                av_get_media_type_string(type), src_filename);
        return ret;
    } else {
        *stream_idx = ret;
        st = fmt_ctx->streams[*stream_idx];
        /* find decoder for the stream */
        dec_ctx = st->codec;
        dec = avcodec_find_decoder(dec_ctx->codec_id);
        if (!dec) {
            fprintf(stderr, "Failed to find %s codec\n",
                    av_get_media_type_string(type));
            return ret;
        }
        if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) {
            fprintf(stderr, "Failed to open %s codec\n",
                    av_get_media_type_string(type));
            return ret;
        }
    }
    return 0;
}
static int get_format_from_sample_fmt(const char **fmt,
                                      enum AVSampleFormat sample_fmt)
{
    int i;
    struct sample_fmt_entry {
        enum AVSampleFormat sample_fmt; const char *fmt_be, *fmt_le;
    } sample_fmt_entries[] = {
        { AV_SAMPLE_FMT_U8,  "u8",    "u8"    },
        { AV_SAMPLE_FMT_S16, "s16be", "s16le" },
        { AV_SAMPLE_FMT_S32, "s32be", "s32le" },
        { AV_SAMPLE_FMT_FLT, "f32be", "f32le" },
        { AV_SAMPLE_FMT_DBL, "f64be", "f64le" },
    };
    *fmt = NULL;
    for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {
        struct sample_fmt_entry *entry = &sample_fmt_entries[i];
        if (sample_fmt == entry->sample_fmt) {
            *fmt = AV_NE(entry->fmt_be, entry->fmt_le);
            return 0;
        }
    }
    fprintf(stderr,
            "sample format %s is not supported as output format\n",
            av_get_sample_fmt_name(sample_fmt));
    return -1;
}
void FFmpegs::demuxer(){
    int ret = 0, got_frame;
    int inEnd = 0;
    src_filename = "/Users/cloud/Documents/iOS/音視頻/TestMusic/Demux/video2/in.mp4";
    video_dst_filename = "/Users/cloud/Documents/iOS/音視頻/TestMusic/Demux/video2/out_video2.yuv";
    audio_dst_filename = "/Users/cloud/Documents/iOS/音視頻/TestMusic/Demux/video2/out_video2.pcm";
    /* register all formats and codecs */
    av_register_all();
    /* open input file, and allocate format context */
    if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) {
        fprintf(stderr, "Could not open source file %s\n", src_filename);
        exit(1);
    }
    /* retrieve stream information */
    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
        fprintf(stderr, "Could not find stream information\n");
        exit(1);
    }
    if (open_codec_context(&video_stream_idx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {
        video_stream = fmt_ctx->streams[video_stream_idx];
        video_dec_ctx = video_stream->codec;
        video_dst_file = fopen(video_dst_filename, "wb");
        if (!video_dst_file) {
            fprintf(stderr, "Could not open destination file %s\n", video_dst_filename);
            ret = 1;
            goto end;
        }
        /* allocate image where the decoded image will be put */
        ret = av_image_alloc(video_dst_data, video_dst_linesize,
                             video_dec_ctx->width, video_dec_ctx->height,
                             video_dec_ctx->pix_fmt, 1);
        if (ret < 0) {
            fprintf(stderr, "Could not allocate raw video buffer\n");
            goto end;
        }
        video_dst_bufsize = ret;
    }
    if (open_codec_context(&audio_stream_idx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) {
        int nb_planes;
        audio_stream = fmt_ctx->streams[audio_stream_idx];
        audio_dec_ctx = audio_stream->codec;
        audio_dst_file = fopen(audio_dst_filename, "wb");
        if (!audio_dst_file) {
            fprintf(stderr, "Could not open destination file %s\n", video_dst_filename);
            ret = 1;
            goto end;
        }
        nb_planes = av_sample_fmt_is_planar(audio_dec_ctx->sample_fmt) ?
            audio_dec_ctx->channels : 1;
        audio_dst_data = (uint8_t **)av_mallocz(sizeof(uint8_t *) * nb_planes);
        if (!audio_dst_data) {
            fprintf(stderr, "Could not allocate audio data buffers\n");
            ret = AVERROR(ENOMEM);
            goto end;
        }
    }
    /* dump input information to stderr */
    av_dump_format(fmt_ctx, 0, src_filename, 0);
    if (!audio_stream && !video_stream) {
        fprintf(stderr, "Could not find audio or video stream in the input, aborting\n");
        ret = 1;
        goto end;
    }
    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate frame\n");
        ret = AVERROR(ENOMEM);
        goto end;
    }
    /* initialize packet, set data to NULL, let the demuxer fill it */
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;
    if (video_stream)
        printf("Demuxing video from file '%s' into '%s'\n", src_filename, video_dst_filename);
    if (audio_stream)
        printf("Demuxing audio from file '%s' into '%s'\n", src_filename, audio_dst_filename);
    /* read frames from the file */
    while (av_read_frame(fmt_ctx, &pkt) >= 0) {
        decode_packet(&got_frame, 0);
        av_free_packet(&pkt);
    }
    /* flush cached frames */
    pkt.data = NULL;
    pkt.size = 0;
    do {
        decode_packet(&got_frame, 1);
    } while (got_frame);

    printf("Demuxing succeeded.\n");
    if (video_stream) {
        printf("Play the output video file with the command:\n"
               "ffplay -f rawvideo -pix_fmt %s -video_size %dx%d %s\n",
               av_get_pix_fmt_name(video_dec_ctx->pix_fmt), video_dec_ctx->width, video_dec_ctx->height,
               video_dst_filename);
    }
    if (audio_stream) {
        const char *fmt;
        if ((ret = get_format_from_sample_fmt(&fmt, audio_dec_ctx->sample_fmt)) < 0)
            goto end;
        printf("Play the output audio file with the command:\n"
               "ffplay -f %s -ac %d -ar %d %s\n",
               fmt, audio_dec_ctx->channels, audio_dec_ctx->sample_rate,
               audio_dst_filename);
    }
end:
    if (video_dec_ctx)
        avcodec_close(video_dec_ctx);
    if (audio_dec_ctx)
        avcodec_close(audio_dec_ctx);
    avformat_close_input(&fmt_ctx);
    if (video_dst_file)
        fclose(video_dst_file);
    if (audio_dst_file)
        fclose(audio_dst_file);
    av_free(frame);
    av_free(video_dst_data[0]);
    av_free(audio_dst_data);
}

此時解碼出來的yuv文件大小為115,458,048字節(jié)眷射,115458048/(7684321.5)計(jì)算出幀數(shù)為232幀

image

通過使用YuvEye工具對比命令行生成的YUV文件和代碼生成的YUV文件發(fā)現(xiàn)尾部少了一幀匙赞,YuvEye比對工具從第0幀開始,官方示例代碼里面也已經(jīng)刷新緩沖區(qū)了妖碉,為何還是少最后一幀呢
image

google搜索答案涌庭,看到這樣一篇博文ffmpeg視頻解碼丟幀問題里面說的丟幀的原因是

讀取的AVPacket都有pts和dts兩個屬性,往復(fù)雜了說視頻幀類型有I/P/B等種類欧宜,我們就以簡單的方式說坐榆,PTS是圖像的展示時間,DTS是圖像的解碼時間冗茸,問題就來了席镀,由于視頻幀類型,很多時候夏漱,PTS基本都是按順序的豪诲,但是DTS卻不是,也就是說這個包的解碼時候需要在下個包解碼之后解碼挂绰,所以此次就不能解碼屎篱,因此獲取不到視頻幀,數(shù)據(jù)被緩存了,如果之后不去主動去取交播,那就真的丟了专肪。

while( av_read_frame(format_ctx_, &packet) >= 0 ) {
    if( packet.stream_index == video_stream_index_ ) {
        avcodec_decode_video2(codec_ctx_, frame, &frameFinished, &packet);
        if( frameFinished ) {
            //...
        }
    }

    av_free_packet(&packet);
}

以上的代碼基本就是常見的使用場景,基本上文件的最后幾幀已經(jīng)丟失了堪侯,雖然很多時候不是太重要嚎尤,可以忽略,畢竟幾幀數(shù)據(jù)在很大幀率下所占的時間很小伍宦。如果需要的話芽死,那如何找回來呢,簡單的辦法就是繼續(xù)空包讀取即可

給出的解決方案:記錄丟掉了多少幀次數(shù)次洼,在正常幀讀取完畢后关贵,進(jìn)行空幀讀取,讀取空幀的次數(shù)就是記錄的丟掉的幀的次數(shù)卖毁,這樣總數(shù)加起來能保持不變揖曾,代碼模板如下

注意,空包解碼的時候必須是packet的data為NULL,size為0亥啦。

while( av_read_frame(format_ctx_, &packet) >= 0 ) {
    if( packet.stream_index == video_stream_index_ ) {
        avcodec_decode_video2(codec_ctx_, frame.get(), &frameFinished, &packet);
        if( frameFinished ) {   
            //...
        } else {
            total_skip++;
        }
    }

    av_free_packet(&packet);
}
    
while(total_skip--) {
    avcodec_decode_video2(codec_ctx_, frame.get(), &frameFinished, &packet);
    if( frameFinished ) {
        //...
    }
}

在代碼中實(shí)現(xiàn)丟幀的空幀讀取炭剪,這個操作就包含了刷新緩沖區(qū)的概念,記錄跳躍幀的次數(shù)翔脱,最后再次讀取跳躍幀次數(shù)次幀

image

于是喜出望外奴拦,請身邊人喝了三瓶可口可樂,以為發(fā)現(xiàn)了規(guī)律總幀數(shù)=正常讀取幀+緩沖區(qū)幀數(shù)+跳躍次數(shù)=230+1+2=233届吁,正好為總幀數(shù)=YUV420P,768x432文件總大小115955712字節(jié)/(7684321.5)=233幀

實(shí)質(zhì)后面發(fā)現(xiàn)緩沖區(qū)中存在2幀错妖,與前面緩沖區(qū)中存在一幀不一致


image
image

再次用兩個mp4文件調(diào)試,一個mp4文件是568x320,YUV420P,發(fā)現(xiàn)如下:命令行生成的yuv文件總大小為66524160字節(jié)/(5683201.5) = 244幀,好像也滿足上面的規(guī)律

圖片.png

image

再次換一個30秒的mp4文件768x432,YUV420P,命令行生成的文件總大小為344881152字節(jié)/(768x432*1.5) = 693幀疚沐,此文件就不符合總幀數(shù)693=正常讀取幀690+緩沖區(qū)幀數(shù)2+跳躍次數(shù)2的規(guī)律了暂氯,690+2+2=694幀多出1幀
image

image

再次換一個60秒的mp4文件768x432,YUV420P,命令行生成的寬高768x432,YUV420P文件的總大小為689264640字節(jié)/(768x432*1.5)= 1385幀,此文件就不符合規(guī)律了1380+2+6=1388就多出兩幀了


image

image

臨時解決辦法:多幀要比少幀好亮蛔,就是最后文件中的數(shù)據(jù)因?yàn)槭钦?緩沖區(qū)的幀+跳躍次數(shù)幀=得到的總幀數(shù)痴施,可能回比實(shí)際幀數(shù)多,但總比缺幀好尔邓,先暫時找到的不是辦法的辦法晾剖,后面理清楚H264的編碼原理后再找解決辦法

跳躍幀次數(shù)+正常幀+緩沖區(qū)幀數(shù)>=總幀數(shù)
230+2+2=234
116453376/(7684321.5)=234幀

image

image

代碼邏輯如下


image

image
#include "ffmpegs.h"
#include <QDebug>
static AVFormatContext *fmt_ctx = NULL;
static AVCodecContext *video_dec_ctx = NULL, *audio_dec_ctx;
static AVStream *video_stream = NULL, *audio_stream = NULL;
static const char *src_filename = NULL;
static const char *video_dst_filename = NULL;
static const char *audio_dst_filename = NULL;
static FILE *video_dst_file = NULL;
static FILE *audio_dst_file = NULL;
static uint8_t *video_dst_data[4] = {NULL};
static int      video_dst_linesize[4];
static int video_dst_bufsize;
static uint8_t **audio_dst_data = NULL;
static int       audio_dst_linesize;
static int audio_dst_bufsize;
static int video_stream_idx = -1, audio_stream_idx = -1;
static AVFrame *frame = NULL;
static AVPacket pkt;
static int video_frame_count = 0;
static int audio_frame_count = 0;
static int videoFrameIdx = 0;
static int audioFrameIdx = 0;
//跳躍了多少次空幀呢
static int skipFrameIdx = 0;

//跳躍幀寫入了多少幀
static int skipFrameWriteIdx = 0;
//跳躍幀里面的尾部幀,防止開頭的跳躍幀被寫入
static int isEndFrame = 0;
//正常幀讀取后刷新緩沖區(qū)刷新了多少幀
static int flushFrameIdx = 0;
//是尾部跳躍幀補(bǔ)齊
static int isSkipFrame = 0;
FFmpegs::FFmpegs()
{

}
static int decode_packet(int *got_frame, int cached)
{
    int ret = 0;
    if (pkt.stream_index == video_stream_idx) {
        /* decode video frame */
        ret = avcodec_decode_video2(video_dec_ctx, frame, got_frame, &pkt);
        if(isEndFrame == 1) qDebug() << "尾部幀刷新后解碼器返回值:" << ret << *got_frame << &pkt;
        if (ret < 0) {
            fprintf(stderr, "Error decoding video frame\n");
            return ret;
        }
        if (*got_frame) {
            qDebug() << "正常視頻幀:" << ++video_frame_count << "幀寬度:" << frame->width;
            if(isEndFrame == 1){//如果是尾部刷新緩沖區(qū)記錄下刷新緩沖區(qū)刷新了多少幀
                flushFrameIdx++;
                qDebug() << "緩沖區(qū)刷新了多少幀" << flushFrameIdx;
            }
            printf("視頻幀-------video_frame%s n:%d coded_n:%d pts:%s\n",
                   cached ? "(cached)" : "",
                   video_frame_count, frame->coded_picture_number,
                   av_ts2timestr(frame->pts, &video_dec_ctx->time_base));
            /* copy decoded frame to destination buffer:
             * this is required since rawvideo expects non aligned data */
            av_image_copy(video_dst_data, video_dst_linesize,
                          (const uint8_t **)(frame->data), frame->linesize,
                          video_dec_ctx->pix_fmt, video_dec_ctx->width, video_dec_ctx->height);
            /* write to rawvideo file */
            fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);
        }else{
            if(isEndFrame==0){//不是尾部刷新緩沖區(qū)進(jìn)入才計(jì)算跳躍次數(shù)
                skipFrameIdx++;
                qDebug() << "當(dāng)前pkt的值" << &pkt << "幀寬度:" << frame->width << "--跳躍次數(shù)--" << skipFrameIdx;
            }
            else{//是尾部跳躍幀數(shù)進(jìn)來梯嗽,將尾部幀寫入文件尾部
                if(isSkipFrame == 1){
                    qDebug() << "got_frame寫入幀為0齿尽,寫入跳躍幀次數(shù)" << ++skipFrameWriteIdx;
                    av_image_copy(video_dst_data, video_dst_linesize,
                                  (const uint8_t **)(frame->data), frame->linesize,
                                  video_dec_ctx->pix_fmt, video_dec_ctx->width, video_dec_ctx->height);
                    fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);
                }
            }
        }
    }
    else if (pkt.stream_index == audio_stream_idx) {
        /* decode audio frame */
        ret = avcodec_decode_audio4(audio_dec_ctx, frame, got_frame, &pkt);
        if (ret < 0) {
            fprintf(stderr, "Error decoding audio frame\n");
            return ret;
        }
        if (*got_frame) {
            printf("音頻幀--------audio_frame%s n:%d nb_samples:%d pts:%s\n",
                   cached ? "(cached)" : "",
                   audio_frame_count++, frame->nb_samples,
                   av_ts2timestr(frame->pts, &audio_dec_ctx->time_base));
            ret = av_samples_alloc(audio_dst_data, &audio_dst_linesize, av_frame_get_channels(frame),
                                   frame->nb_samples, (AVSampleFormat)frame->format, 1);
            if (ret < 0) {
                fprintf(stderr, "Could not allocate audio buffer\n");
                return AVERROR(ENOMEM);
            }
            /* TODO: extend return code of the av_samples_* functions so that this call is not needed */
            audio_dst_bufsize =
                av_samples_get_buffer_size(NULL, av_frame_get_channels(frame),
                                           frame->nb_samples, (AVSampleFormat)frame->format, 1);
            /* copy audio data to destination buffer:
             * this is required since rawaudio expects non aligned data */
            av_samples_copy(audio_dst_data, frame->data, 0, 0,
                            frame->nb_samples, av_frame_get_channels(frame), (AVSampleFormat)frame->format);
            /* write to rawaudio file */
            printf("單次寫入音頻幀的大小 %d\n寫了多少次 %d",audio_dst_bufsize,++audioFrameIdx);
            fwrite(audio_dst_data[0], 1, audio_dst_bufsize, audio_dst_file);
            av_freep(&audio_dst_data[0]);
        }
    }
    return ret;
}
static int open_codec_context(int *stream_idx,
                              AVFormatContext *fmt_ctx, enum AVMediaType type)
{
    int ret;
    AVStream *st;
    AVCodecContext *dec_ctx = NULL;
    AVCodec *dec = NULL;
    ret = av_find_best_stream(fmt_ctx, type, -1, -1, NULL, 0);
    if (ret < 0) {
        fprintf(stderr, "Could not find %s stream in input file '%s'\n",
                av_get_media_type_string(type), src_filename);
        return ret;
    } else {
        *stream_idx = ret;
        st = fmt_ctx->streams[*stream_idx];
        /* find decoder for the stream */
        dec_ctx = st->codec;
        dec = avcodec_find_decoder(dec_ctx->codec_id);
        if (!dec) {
            fprintf(stderr, "Failed to find %s codec\n",
                    av_get_media_type_string(type));
            return ret;
        }
        if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) {
            fprintf(stderr, "Failed to open %s codec\n",
                    av_get_media_type_string(type));
            return ret;
        }
    }
    return 0;
}
static int get_format_from_sample_fmt(const char **fmt,
                                      enum AVSampleFormat sample_fmt)
{
    int i;
    struct sample_fmt_entry {
        enum AVSampleFormat sample_fmt; const char *fmt_be, *fmt_le;
    } sample_fmt_entries[] = {
        { AV_SAMPLE_FMT_U8,  "u8",    "u8"    },
        { AV_SAMPLE_FMT_S16, "s16be", "s16le" },
        { AV_SAMPLE_FMT_S32, "s32be", "s32le" },
        { AV_SAMPLE_FMT_FLT, "f32be", "f32le" },
        { AV_SAMPLE_FMT_DBL, "f64be", "f64le" },
    };
    *fmt = NULL;
    for (i = 0; i < FF_ARRAY_ELEMS(sample_fmt_entries); i++) {
        struct sample_fmt_entry *entry = &sample_fmt_entries[i];
        if (sample_fmt == entry->sample_fmt) {
            *fmt = AV_NE(entry->fmt_be, entry->fmt_le);
            return 0;
        }
    }
    fprintf(stderr,
            "sample format %s is not supported as output format\n",
            av_get_sample_fmt_name(sample_fmt));
    return -1;
}
void FFmpegs::demuxer(){
    int ret = 0, got_frame;
    int inEnd = 0;
    src_filename = "/Users/cloud/Documents/iOS/音視頻/TestMusic/Demux/in.mp4";
    video_dst_filename = "/Users/cloud/Documents/iOS/音視頻/TestMusic/Demux/out_video2_optimize.yuv";
    audio_dst_filename = "/Users/cloud/Documents/iOS/音視頻/TestMusic/Demux/out_video2_optimize.pcm";
    /* register all formats and codecs */
    av_register_all();
    /* open input file, and allocate format context */
    if (avformat_open_input(&fmt_ctx, src_filename, NULL, NULL) < 0) {
        fprintf(stderr, "Could not open source file %s\n", src_filename);
        exit(1);
    }
    /* retrieve stream information */
    if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
        fprintf(stderr, "Could not find stream information\n");
        exit(1);
    }
    if (open_codec_context(&video_stream_idx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {
        video_stream = fmt_ctx->streams[video_stream_idx];
        video_dec_ctx = video_stream->codec;
        video_dst_file = fopen(video_dst_filename, "wb");
        if (!video_dst_file) {
            fprintf(stderr, "Could not open destination file %s\n", video_dst_filename);
            ret = 1;
            goto end;
        }
        /* allocate image where the decoded image will be put */
        ret = av_image_alloc(video_dst_data, video_dst_linesize,
                             video_dec_ctx->width, video_dec_ctx->height,
                             video_dec_ctx->pix_fmt, 1);
        if (ret < 0) {
            fprintf(stderr, "Could not allocate raw video buffer\n");
            goto end;
        }
        video_dst_bufsize = ret;
    }
    if (open_codec_context(&audio_stream_idx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) {
        int nb_planes;
        audio_stream = fmt_ctx->streams[audio_stream_idx];
        audio_dec_ctx = audio_stream->codec;
        audio_dst_file = fopen(audio_dst_filename, "wb");
        if (!audio_dst_file) {
            fprintf(stderr, "Could not open destination file %s\n", video_dst_filename);
            ret = 1;
            goto end;
        }
        nb_planes = av_sample_fmt_is_planar(audio_dec_ctx->sample_fmt) ?
            audio_dec_ctx->channels : 1;
        audio_dst_data = (uint8_t **)av_mallocz(sizeof(uint8_t *) * nb_planes);
        if (!audio_dst_data) {
            fprintf(stderr, "Could not allocate audio data buffers\n");
            ret = AVERROR(ENOMEM);
            goto end;
        }
    }
    /* dump input information to stderr */
    av_dump_format(fmt_ctx, 0, src_filename, 0);
    if (!audio_stream && !video_stream) {
        fprintf(stderr, "Could not find audio or video stream in the input, aborting\n");
        ret = 1;
        goto end;
    }
    frame = av_frame_alloc();
    if (!frame) {
        fprintf(stderr, "Could not allocate frame\n");
        ret = AVERROR(ENOMEM);
        goto end;
    }
    /* initialize packet, set data to NULL, let the demuxer fill it */
    av_init_packet(&pkt);
    pkt.data = NULL;
    pkt.size = 0;
    if (video_stream)
        printf("Demuxing video from file '%s' into '%s'\n", src_filename, video_dst_filename);
    if (audio_stream)
        printf("Demuxing audio from file '%s' into '%s'\n", src_filename, audio_dst_filename);
    /* read frames from the file */
    //跳躍幀次數(shù)+正常幀+緩沖區(qū)幀數(shù)>=總幀數(shù)
    //正常幀
    while (av_read_frame(fmt_ctx, &pkt) >= 0) {
        decode_packet(&got_frame, 0);
        av_free_packet(&pkt);
    }
    /* flush cached frames */
    pkt.data = NULL;
    pkt.size = 0;
    //刷新緩沖區(qū)幀
    do {
        qDebug() << "進(jìn)入刷新緩沖區(qū)空幀讀取" << got_frame;
        isEndFrame = 1;
        decode_packet(&got_frame, 1);
    } while (got_frame);
    //跳躍幀
    while(skipFrameIdx--){
        isSkipFrame = 1;
        qDebug() << "進(jìn)入跳躍幀讀取" << skipFrameIdx;
        decode_packet(&got_frame, 1);
    }
    printf("Demuxing succeeded.\n");
    if (video_stream) {
        printf("Play the output video file with the command:\n"
               "ffplay -f rawvideo -pix_fmt %s -video_size %dx%d %s\n",
               av_get_pix_fmt_name(video_dec_ctx->pix_fmt), video_dec_ctx->width, video_dec_ctx->height,
               video_dst_filename);
    }
    if (audio_stream) {
        const char *fmt;
        if ((ret = get_format_from_sample_fmt(&fmt, audio_dec_ctx->sample_fmt)) < 0)
            goto end;
        printf("Play the output audio file with the command:\n"
               "ffplay -f %s -ac %d -ar %d %s\n",
               fmt, audio_dec_ctx->channels, audio_dec_ctx->sample_rate,
               audio_dst_filename);
    }
end:
    if (video_dec_ctx)
        avcodec_close(video_dec_ctx);
    if (audio_dec_ctx)
        avcodec_close(audio_dec_ctx);
    avformat_close_input(&fmt_ctx);
    if (video_dst_file)
        fclose(video_dst_file);
    if (audio_dst_file)
        fclose(audio_dst_file);
    av_free(frame);
    av_free(video_dst_data[0]);
    av_free(audio_dst_data);
}

II.新版本的解碼代碼如何優(yōu)化呢

avcodec_send_packet和avcodec_receive_frame代碼中沒有g(shù)ot_frame標(biāo)識是否獲取到了完整一幀,不知道跳躍了多少次

H264編碼的原理是:I幀P幀B幀混合組成灯节,P幀的編解碼要依賴前面I幀或P幀循头,B幀的編解碼要依賴前后I幀和P幀绵估,若最后截取的字符串是B幀呢,那么它的解封裝要依賴后面的B幀或P幀卡骂,會導(dǎo)致最后的幾幀無法解碼出來

3.還有一個問題国裳,ffprobe命令檢測出來的幀數(shù)和命令行生成的實(shí)際幀數(shù)也不一致,命令行生成的yuv文件為233幀,ffprobe檢測出來為232幀全跨,ffprobe檢測出來的總是少幾幀

ffprobe -v error -count_frames -select_streams v:0 -show_entries stream=nb_read_frames -of default=nokey=1:noprint_wrappers=1 input.mp4

圖片.png

未完待續(xù)缝左。。浓若。

GitHub上ffmpeg官方補(bǔ)丁地址patch
查閱文檔《H.264編碼》
I幀渺杉,P幀,B幀的本質(zhì)

I幀(I Picture挪钓、I Frame是越、Intra Coded Picture),譯為:幀內(nèi)編碼圖像碌上,也叫做關(guān)鍵幀(Keyframe)
是視頻的第一幀倚评,也是GOP的第一幀,一個GOP只有一個I幀
編碼
對整幀圖像數(shù)據(jù)進(jìn)行編碼
解碼
僅用當(dāng)前I幀的編碼數(shù)據(jù)就可以解碼出完整的圖像
是一種自帶全部信息的獨(dú)立幀馏予,無需參考其他圖像便可獨(dú)立進(jìn)行解碼天梧,可以簡單理解為一張靜態(tài)圖像

P幀(P Picture、P Frame吗蚌、Predictive Coded Picture)腿倚,譯為:預(yù)測編碼圖像
編碼
并不會對整幀圖像數(shù)據(jù)進(jìn)行編碼
以前面的I幀或P幀作為參考幀,只編碼當(dāng)前P幀與參考幀的差異數(shù)據(jù)
解碼
需要先解碼出前面的參考幀蚯妇,再結(jié)合差異數(shù)據(jù)解碼出當(dāng)前P幀完整的圖像

B幀(B Picture、B Frame暂筝、Bipredictive Coded Picture)箩言,譯為:前后預(yù)測編碼圖像
編碼
并不會對整幀圖像數(shù)據(jù)進(jìn)行編碼
同時以前面、后面的I幀或P幀作為參考幀焕襟,只編碼當(dāng)前B幀與前后參考幀的差異數(shù)據(jù)
因?yàn)榭蓞⒖嫉膸兌嗔嗽墒眨灾恍枰鎯Ω俚牟町悢?shù)據(jù)
解碼
需要先解碼出前后的參考幀,再結(jié)合差異數(shù)據(jù)解碼出當(dāng)前B幀完整的圖像

最后編輯于
?著作權(quán)歸作者所有,轉(zhuǎn)載或內(nèi)容合作請聯(lián)系作者
  • 序言:七十年代末鸵赖,一起剝皮案震驚了整個濱河市务漩,隨后出現(xiàn)的幾起案子,更是在濱河造成了極大的恐慌它褪,老刑警劉巖饵骨,帶你破解...
    沈念sama閱讀 218,036評論 6 506
  • 序言:濱河連續(xù)發(fā)生了三起死亡事件啊胶,死亡現(xiàn)場離奇詭異春锋,居然都是意外死亡,警方通過查閱死者的電腦和手機(jī)痹屹,發(fā)現(xiàn)死者居然都...
    沈念sama閱讀 93,046評論 3 395
  • 文/潘曉璐 我一進(jìn)店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來轮洋,“玉大人制市,你說我怎么就攤上這事”子瑁” “怎么了祥楣?”我有些...
    開封第一講書人閱讀 164,411評論 0 354
  • 文/不壞的土叔 我叫張陵,是天一觀的道長汉柒。 經(jīng)常有香客問我误褪,道長,這世上最難降的妖魔是什么竭翠? 我笑而不...
    開封第一講書人閱讀 58,622評論 1 293
  • 正文 為了忘掉前任振坚,我火速辦了婚禮,結(jié)果婚禮上斋扰,老公的妹妹穿的比我還像新娘渡八。我一直安慰自己,他們只是感情好传货,可當(dāng)我...
    茶點(diǎn)故事閱讀 67,661評論 6 392
  • 文/花漫 我一把揭開白布屎鳍。 她就那樣靜靜地躺著,像睡著了一般问裕。 火紅的嫁衣襯著肌膚如雪逮壁。 梳的紋絲不亂的頭發(fā)上,一...
    開封第一講書人閱讀 51,521評論 1 304
  • 那天粮宛,我揣著相機(jī)與錄音窥淆,去河邊找鬼。 笑死巍杈,一個胖子當(dāng)著我的面吹牛忧饭,可吹牛的內(nèi)容都是我干的。 我是一名探鬼主播筷畦,決...
    沈念sama閱讀 40,288評論 3 418
  • 文/蒼蘭香墨 我猛地睜開眼词裤,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了鳖宾?” 一聲冷哼從身側(cè)響起吼砂,我...
    開封第一講書人閱讀 39,200評論 0 276
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎鼎文,沒想到半個月后渔肩,有當(dāng)?shù)厝嗽跇淞掷锇l(fā)現(xiàn)了一具尸體,經(jīng)...
    沈念sama閱讀 45,644評論 1 314
  • 正文 獨(dú)居荒郊野嶺守林人離奇死亡漂问,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內(nèi)容為張勛視角 年9月15日...
    茶點(diǎn)故事閱讀 37,837評論 3 336
  • 正文 我和宋清朗相戀三年赖瞒,在試婚紗的時候發(fā)現(xiàn)自己被綠了女揭。 大學(xué)時的朋友給我發(fā)了我未婚夫和他白月光在一起吃飯的照片。...
    茶點(diǎn)故事閱讀 39,953評論 1 348
  • 序言:一個原本活蹦亂跳的男人離奇死亡栏饮,死狀恐怖吧兔,靈堂內(nèi)的尸體忽然破棺而出,到底是詐尸還是另有隱情袍嬉,我是刑警寧澤境蔼,帶...
    沈念sama閱讀 35,673評論 5 346
  • 正文 年R本政府宣布,位于F島的核電站伺通,受9級特大地震影響箍土,放射性物質(zhì)發(fā)生泄漏。R本人自食惡果不足惜罐监,卻給世界環(huán)境...
    茶點(diǎn)故事閱讀 41,281評論 3 329
  • 文/蒙蒙 一吴藻、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧弓柱,春花似錦沟堡、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 31,889評論 0 22
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至屁药,卻和暖如春粥血,著一層夾襖步出監(jiān)牢的瞬間,已是汗流浹背酿箭。 一陣腳步聲響...
    開封第一講書人閱讀 33,011評論 1 269
  • 我被黑心中介騙來泰國打工复亏, 沒想到剛下飛機(jī)就差點(diǎn)兒被人妖公主榨干…… 1. 我叫王不留,地道東北人缭嫡。 一個月前我還...
    沈念sama閱讀 48,119評論 3 370
  • 正文 我出身青樓蜓耻,卻偏偏與公主長得像,于是被迫代替她去往敵國和親械巡。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當(dāng)晚...
    茶點(diǎn)故事閱讀 44,901評論 2 355

推薦閱讀更多精彩內(nèi)容