FFmpeg视频解码尾部掉帧

《音视频文章汇总》
接触ffmpeg有一段时间了,现遇到一个视频解码尾部掉帧的问题,记录下,缓冲区已经刷过,但还是尾部少帧,虽然尾部少一帧两帧对视频整体效果没啥影响(一般视频疫苗播放25帧),但学东西就学透吧

FFmpeg从MP4文件中抽取yuv纯视频文件有两种方式

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

ffmpeg -i in.mp4 out_commandline.yuv

in.mp4文件是一个768x432,yuv420p,23fps的视频文件,抽取出来的yuv文件的总大小为115,955,712字节,可以计算出总帧数115955712/(7684321.5) = 233帧数

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路径进行解码


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字节,115458048/(7684321.5)计算出帧数为232帧

image

通过使用YuvEye工具对比命令行生成的YUV文件和代码生成的YUV文件发现尾部少了一帧,YuvEye比对工具从第0帧开始,官方示例代码里面也已经刷新缓冲区了,为何还是少最后一帧呢
image

google搜索答案,看到这样一篇博文ffmpeg视频解码丢帧问题里面说的丢帧的原因是

读取的AVPacket都有pts和dts两个属性,往复杂了说视频帧类型有I/P/B等种类,我们就以简单的方式说,PTS是图像的展示时间,DTS是图像的解码时间,问题就来了,由于视频帧类型,很多时候,PTS基本都是按顺序的,但是DTS却不是,也就是说这个包的解码时候需要在下个包解码之后解码,所以此次就不能解码,因此获取不到视频帧,数据被缓存了,如果之后不去主动去取,那就真的丢了。

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);
}

以上的代码基本就是常见的使用场景,基本上文件的最后几帧已经丢失了,虽然很多时候不是太重要,可以忽略,毕竟几帧数据在很大帧率下所占的时间很小。如果需要的话,那如何找回来呢,简单的办法就是继续空包读取即可

给出的解决方案:记录丢掉了多少帧次数,在正常帧读取完毕后,进行空帧读取,读取空帧的次数就是记录的丢掉的帧的次数,这样总数加起来能保持不变,代码模板如下

注意,空包解码的时候必须是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 ) {
        //...
    }
}

在代码中实现丢帧的空帧读取,这个操作就包含了刷新缓冲区的概念,记录跳跃帧的次数,最后再次读取跳跃帧次数次帧

image

于是喜出望外,请身边人喝了三瓶可口可乐,以为发现了规律总帧数=正常读取帧+缓冲区帧数+跳跃次数=230+1+2=233,正好为总帧数=YUV420P,768x432文件总大小115955712字节/(7684321.5)=233帧

实质后面发现缓冲区中存在2帧,与前面缓冲区中存在一帧不一致


image
image

再次用两个mp4文件调试,一个mp4文件是568x320,YUV420P,发现如下:命令行生成的yuv文件总大小为66524160字节/(5683201.5) = 244帧,好像也满足上面的规律

图片.png

image

再次换一个30秒的mp4文件768x432,YUV420P,命令行生成的文件总大小为344881152字节/(768x432*1.5) = 693帧,此文件就不符合总帧数693=正常读取帧690+缓冲区帧数2+跳跃次数2的规律了,690+2+2=694帧多出1帧
image

image

再次换一个60秒的mp4文件768x432,YUV420P,命令行生成的宽高768x432,YUV420P文件的总大小为689264640字节/(768x432*1.5)= 1385帧,此文件就不符合规律了1380+2+6=1388就多出两帧了


image

image

临时解决办法:多帧要比少帧好,就是最后文件中的数据因为是正常帧+缓冲区的帧+跳跃次数帧=得到的总帧数,可能回比实际帧数多,但总比缺帧好,先暂时找到的不是办法的办法,后面理清楚H264的编码原理后再找解决办法

跳跃帧次数+正常帧+缓冲区帧数>=总帧数
230+2+2=234
116453376/(7684321.5)=234帧

image

image

代码逻辑如下


image

image
#include "ffmpegs.h"
#include 
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;
//正常帧读取后刷新缓冲区刷新了多少帧
static int flushFrameIdx = 0;
//是尾部跳跃帧补齐
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){//如果是尾部刷新缓冲区记录下刷新缓冲区刷新了多少帧
                flushFrameIdx++;
                qDebug() << "缓冲区刷新了多少帧" << 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){//不是尾部刷新缓冲区进入才计算跳跃次数
                skipFrameIdx++;
                qDebug() << "当前pkt的值" << &pkt << "帧宽度:" << frame->width << "--跳跃次数--" << skipFrameIdx;
            }
            else{//是尾部跳跃帧数进来,将尾部帧写入文件尾部
                if(isSkipFrame == 1){
                    qDebug() << "got_frame写入帧为0,写入跳跃帧次数" << ++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 */
    //跳跃帧次数+正常帧+缓冲区帧数>=总帧数
    //正常帧
    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 {
        qDebug() << "进入刷新缓冲区空帧读取" << got_frame;
        isEndFrame = 1;
        decode_packet(&got_frame, 1);
    } while (got_frame);
    //跳跃帧
    while(skipFrameIdx--){
        isSkipFrame = 1;
        qDebug() << "进入跳跃帧读取" << 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.新版本的解码代码如何优化呢

avcodec_send_packet和avcodec_receive_frame代码中没有got_frame标识是否获取到了完整一帧,不知道跳跃了多少次

H264编码的原理是:I帧P帧B帧混合组成,P帧的编解码要依赖前面I帧或P帧,B帧的编解码要依赖前后I帧和P帧,若最后截取的字符串是B帧呢,那么它的解封装要依赖后面的B帧或P帧,会导致最后的几帧无法解码出来

3.还有一个问题,ffprobe命令检测出来的帧数和命令行生成的实际帧数也不一致,命令行生成的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

未完待续。。。

GitHub上ffmpeg官方补丁地址patch
查阅文档《H.264编码》
I帧,P帧,B帧的本质

I帧(I Picture、I Frame、Intra Coded Picture),译为:帧内编码图像,也叫做关键帧(Keyframe)
是视频的第一帧,也是GOP的第一帧,一个GOP只有一个I帧
编码
对整帧图像数据进行编码
解码
仅用当前I帧的编码数据就可以解码出完整的图像
是一种自带全部信息的独立帧,无需参考其他图像便可独立进行解码,可以简单理解为一张静态图像

P帧(P Picture、P Frame、Predictive Coded Picture),译为:预测编码图像
编码
并不会对整帧图像数据进行编码
以前面的I帧或P帧作为参考帧,只编码当前P帧与参考帧的差异数据
解码
需要先解码出前面的参考帧,再结合差异数据解码出当前P帧完整的图像

B帧(B Picture、B Frame、Bipredictive Coded Picture),译为:前后预测编码图像
编码
并不会对整帧图像数据进行编码
同时以前面、后面的I帧或P帧作为参考帧,只编码当前B帧与前后参考帧的差异数据
因为可参考的帧变多了,所以只需要存储更少的差异数据
解码
需要先解码出前后的参考帧,再结合差异数据解码出当前B帧完整的图像

你可能感兴趣的:(FFmpeg视频解码尾部掉帧)