从头用脚分析FFmpeg源码 - avcodec_send_frame | avcodec_receive_packet

2023-05-16

avcodec_send_frame和avcodec_receive_packet 作用

相对应avcodec_send_packet | avcodec_receive_frame而言,avcodec_send_frame | avcodec_receive_packet 是编码用的。

/**
 * Supply a raw video or audio frame to the encoder. Use avcodec_receive_packet()
 * to retrieve buffered output packets.
 * @return 0 on success, otherwise negative error code:
 *      AVERROR(EAGAIN):   input is not accepted in the current state - user
 *                         must read output with avcodec_receive_packet() (once
 *                         all output is read, the packet should be resent, and
 *                         the call will not fail with EAGAIN).
 *      AVERROR_EOF:       the encoder has been flushed, and no new frames can
 *                         be sent to it
 *      AVERROR(EINVAL):   codec not opened, refcounted_frames not set, it is a
 *                         decoder, or requires flush
 *      AVERROR(ENOMEM):   failed to add packet to internal queue, or similar
 *      other errors: legitimate encoding errors
 */
int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);

/**
 * Read encoded data from the encoder.
 * @return 0 on success, otherwise negative error code:
 *      AVERROR(EAGAIN):   output is not available in the current state - user
 *                         must try to send input
 *      AVERROR_EOF:       the encoder has been fully flushed, and there will be
 *                         no more output packets
 *      AVERROR(EINVAL):   codec not opened, or it is a decoder
 *      other errors: legitimate encoding errors
 */
int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);

avcodec_send_frame源码

把frame送到avci->buffer_frame中,然后根据avci->buffer_pkt中是否存在编码完成后的cache,判断是否调用encode_receive_packet_internal来编码。

int attribute_align_arg avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;

	//检测是否open,是否为编码器
    if (!avcodec_is_open(avctx) || !av_codec_is_encoder(avctx->codec))
        return AVERROR(EINVAL);

	//frame为空过,这个时候不能再次调用avcodec_send_frame编码,需要flush
	//对于x264来说,调用flush无效,需要重新创建AVCodecContext
    if (avci->draining)
        return AVERROR_EOF;

    if (avci->buffer_frame->data[0])
        return AVERROR(EAGAIN);

	//为null的话,就标记draining为1,之后会在编码阶段使用
    if (!frame) {
        avci->draining = 1;
    } else {
    	//avctx->internal->buffer_frame引用frame,之后编码阶段就可以直接使用buffer_frame
        ret = encode_send_frame_internal(avctx, frame);
        if (ret < 0)
            return ret;
    }

	//当前没有存放的编码完成后的cache
    if (!avci->buffer_pkt->data && !avci->buffer_pkt->side_data) {
    	//调用编码方法
        ret = encode_receive_packet_internal(avctx, avci->buffer_pkt);
        if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)
            return ret;
    }

    return 0;
}

avcodec_receive_packet 源码

如果avctx->internal->buffer_pkt中存在已经编码后的cache,就用buffer_pkt,不存在就调用encode_receive_packet_internal编码。

int attribute_align_arg avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;

    av_packet_unref(avpkt);

	//检测是否open,是否为编码器
    if (!avcodec_is_open(avctx) || !av_codec_is_encoder(avctx->codec))
        return AVERROR(EINVAL);

	//是否使用cache
    if (avci->buffer_pkt->data || avci->buffer_pkt->side_data) {
        av_packet_move_ref(avpkt, avci->buffer_pkt);
    } else {
        ret = encode_receive_packet_internal(avctx, avpkt);
        if (ret < 0)
            return ret;
    }

    return 0;
}

encode_send_frame_internal源码

主要作用是把输入的AVFrame复制到avctx->internal->buffer_frame中。
如果是音频,还会根据codec的capabilities类型,判断输入的AVFrame的采样数量是否足够,不足够就返回AVERROR(EINVAL)。

static int encode_send_frame_internal(AVCodecContext *avctx, const AVFrame *src)
{
    AVCodecInternal *avci = avctx->internal;
    AVFrame *dst = avci->buffer_frame;
    int ret;

	//检测输入的音频Fram采样数量是否满足编码器的capabilities
    if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {
        /* extract audio service type metadata */
        AVFrameSideData *sd = av_frame_get_side_data(src, AV_FRAME_DATA_AUDIO_SERVICE_TYPE);
        if (sd && sd->size >= sizeof(enum AVAudioServiceType))
            avctx->audio_service_type = *(enum AVAudioServiceType*)sd->data;

        /* check for valid frame size */
        if (avctx->codec->capabilities & AV_CODEC_CAP_SMALL_LAST_FRAME) {
            if (src->nb_samples > avctx->frame_size) {
                av_log(avctx, AV_LOG_ERROR, "more samples than frame size\n");
                return AVERROR(EINVAL);
            }
        } else if (!(avctx->codec->capabilities & AV_CODEC_CAP_VARIABLE_FRAME_SIZE)) {
            /* if we already got an undersized frame, that must have been the last */
            if (avctx->internal->last_audio_frame) {
                av_log(avctx, AV_LOG_ERROR, "frame_size (%d) was not respected for a non-last frame\n", avctx->frame_size);
                return AVERROR(EINVAL);
            }

            if (src->nb_samples < avctx->frame_size) {
                ret = pad_last_frame(avctx, dst, src);
                if (ret < 0)
                    return ret;

                avctx->internal->last_audio_frame = 1;
            } else if (src->nb_samples > avctx->frame_size) {
                av_log(avctx, AV_LOG_ERROR, "nb_samples (%d) != frame_size (%d)\n", src->nb_samples, avctx->frame_size);
                return AVERROR(EINVAL);
            }
        }
    }

	//将输入的src引用到avctx->internal->buffer_frame中
    if (!dst->data[0]) {
        ret = av_frame_ref(dst, src);
        if (ret < 0)
             return ret;
    }

    return 0;
}

encode_receive_packet_internal 源码

编码阶段重要函数,这个函数会判断所使用的AVCodec是否已经实现了receive_packet函数,从而判断是否是调用receive_packet编码还是调用encode_simple_receive_packet编码。

static int encode_receive_packet_internal(AVCodecContext *avctx, AVPacket *avpkt)
{
    AVCodecInternal *avci = avctx->internal;
    int ret;

	// 已经输入null,且已经返回了,这个时候就不能在调用编码方法
    if (avci->draining_done)
        return AVERROR_EOF;

    av_assert0(!avpkt->data && !avpkt->side_data);

    if (avctx->codec->type == AVMEDIA_TYPE_VIDEO) {
        if ((avctx->flags & AV_CODEC_FLAG_PASS1) && avctx->stats_out)
            avctx->stats_out[0] = '\0';
        if (av_image_check_size2(avctx->width, avctx->height, avctx->max_pixels, AV_PIX_FMT_NONE, 0, avctx))
            return AVERROR(EINVAL);
    }

	//硬编码器一般都会实现receive_packet。
    if (avctx->codec->receive_packet) {
        ret = avctx->codec->receive_packet(avctx, avpkt);
        if (ret < 0)
            av_packet_unref(avpkt);
        else
            // Encoders must always return ref-counted buffers.
            // Side-data only packets have no data and can be not ref-counted.
            av_assert0(!avpkt->data || avpkt->buf);
    } else
    	//软编码基本都走这里
        ret = encode_simple_receive_packet(avctx, avpkt);

    if (ret == AVERROR_EOF)
        avci->draining_done = 1;

    return ret;
}

encode_simple_receive_packet 源码

这个函数通过while循环不停的调用encode_simple_internal编码。

static int encode_simple_receive_packet(AVCodecContext *avctx, AVPacket *avpkt)
{
    int ret;

    while (!avpkt->data && !avpkt->side_data) {
        ret = encode_simple_internal(avctx, avpkt);
        if (ret < 0)
            return ret;
    }

    return 0;
}

encode_simple_internal源码

软编码的大部分都会调用到这个函数中,在这种函数中,通过判断是否是多线程编码,调用不同的编码函数。

static int encode_simple_internal(AVCodecContext *avctx, AVPacket *avpkt)
{
    AVCodecInternal   *avci = avctx->internal;
    EncodeSimpleContext *es = &avci->es;
    AVFrame          *frame = es->in_frame;
    int got_packet;
    int ret;

    if (avci->draining_done)
        return AVERROR_EOF;

    if (!frame->buf[0] && !avci->draining) {
        av_frame_unref(frame);
        // avctx->internal->buffer_frame送到frame中。
        ret = ff_encode_get_frame(avctx, frame);
        if (ret < 0 && ret != AVERROR_EOF)
            return ret;
    }

    if (!frame->buf[0]) {
        if (!(avctx->codec->capabilities & AV_CODEC_CAP_DELAY ||
              (avci->frame_thread_encoder && avctx->active_thread_type & FF_THREAD_FRAME)))
            return AVERROR_EOF;

        // Flushing is signaled with a NULL frame
        frame = NULL;
    }

    got_packet = 0;

    av_assert0(avctx->codec->encode2);

	//多线程编码
    if (CONFIG_FRAME_THREAD_ENCODER &&
        avci->frame_thread_encoder && (avctx->active_thread_type & FF_THREAD_FRAME))
        /* This might modify frame, but it doesn't matter, because
         * the frame properties used below are not used for video
         * (due to the delay inherent in frame threaded encoding, it makes
         *  no sense to use the properties of the current frame anyway). */
        ret = ff_thread_video_encode_frame(avctx, avpkt, frame, &got_packet);
    else {
    	//单线程,直接调用AVCodec中的encode2方法编码,对应的实现方式,就是对应的编码器实现
        ret = avctx->codec->encode2(avctx, avpkt, frame, &got_packet);
        if (avctx->codec->type == AVMEDIA_TYPE_VIDEO && !ret && got_packet &&
            !(avctx->codec->capabilities & AV_CODEC_CAP_DELAY))
            avpkt->pts = avpkt->dts = frame->pts;
    }

    av_assert0(ret <= 0);

    emms_c();

    if (!ret && got_packet) {
    	// 返回正常且got_packet,这个是否就引用一下 avpkt,然后直接到end,不设置draining_done
        if (avpkt->data) {
            ret = av_packet_make_refcounted(avpkt);
            if (ret < 0)
                goto end;
        }

        if (frame && !(avctx->codec->capabilities & AV_CODEC_CAP_DELAY)) {
            if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {
                if (avpkt->pts == AV_NOPTS_VALUE)
                    avpkt->pts = frame->pts;
                if (!avpkt->duration)
                    avpkt->duration = ff_samples_to_time_base(avctx,
                                                              frame->nb_samples);
            }
        }
        if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {
            /* NOTE: if we add any audio encoders which output non-keyframe packets,
             *       this needs to be moved to the encoders, but for now we can do it
             *       here to simplify things */
            avpkt->flags |= AV_PKT_FLAG_KEY;
            avpkt->dts = avpkt->pts;
        }
    }

	//如果这个时候是输入了null,且这次没有得到编码的buffer,
	//即所有的编码完成的cache都已经输出了,这个时候就标识draining_done,不再允许调用avcodec_receive_packet
    if (avci->draining && !got_packet)
        avci->draining_done = 1;

end:
    if (ret < 0 || !got_packet)
        av_packet_unref(avpkt);

    if (frame) {
        if (!ret)
            avctx->frame_number++;
        av_frame_unref(frame);
    }

    if (got_packet)
        // Encoders must always return ref-counted buffers.
        // Side-data only packets have no data and can be not ref-counted.
        av_assert0(!avpkt->data || avpkt->buf);

    return ret;
}

ff_thread_video_encode_frame 源码

多线程编码,配合调用avcodec_open2 时,打开的编码线程。

int ff_thread_video_encode_frame(AVCodecContext *avctx, AVPacket *pkt,
                                 AVFrame *frame, int *got_packet_ptr)
{
    ThreadContext *c = avctx->internal->frame_thread_encoder;
    //多线程编码中,是通过Task传递编码Frame和编码完成后Packet的
    Task *outtask;

    av_assert1(!*got_packet_ptr);

    if(frame){
    	//引用 frame
        av_frame_move_ref(c->tasks[c->task_index].indata, frame);

        pthread_mutex_lock(&c->task_fifo_mutex);
        c->task_index = (c->task_index + 1) % c->max_tasks;
        //发送task_fifo_cond信号量, 编码线程中等待task_fifo_cond这个信号量编码。
        pthread_cond_signal(&c->task_fifo_cond);
        pthread_mutex_unlock(&c->task_fifo_mutex);
    }

    outtask = &c->tasks[c->finished_task_index];
    pthread_mutex_lock(&c->finished_task_mutex);
    /* The access to task_index in the following code is ok,
     * because it is only ever changed by the main thread. */
    if (c->task_index == c->finished_task_index ||
        (frame && !outtask->finished &&
         (c->task_index - c->finished_task_index + c->max_tasks) % c->max_tasks <= avctx->thread_count)) {
            pthread_mutex_unlock(&c->finished_task_mutex);
            return 0;
        }
    //等待finished_task_cond信号量
    while (!outtask->finished) {
        pthread_cond_wait(&c->finished_task_cond, &c->finished_task_mutex);
    }
    pthread_mutex_unlock(&c->finished_task_mutex);
    /* We now own outtask completely: No worker thread touches it any more,
     * because there is no outstanding task with this index. */
    outtask->finished = 0;
    //引用编码后的pkt
    av_packet_move_ref(pkt, outtask->outdata);
    if(pkt->data)
        *got_packet_ptr = 1;
    c->finished_task_index = (c->finished_task_index + 1) % c->max_tasks;

    return outtask->return_code;
}
本文内容由网友自发贡献,版权归原作者所有,本站不承担相应法律责任。如您发现有涉嫌抄袭侵权的内容,请联系:hwhale#tublm.com(使用前将#替换为@)

从头用脚分析FFmpeg源码 - avcodec_send_frame | avcodec_receive_packet 的相关文章

  • 如何使用 FFMPEG 获得最佳的 FLV 整体质量?

    我希望以最小的文件大小实现最佳质量的 FLV 毕竟 这不是每个人的目标吗 如果这有什么影响的话 这些视频将会被直播 目前 我的视频宽度不超过 320 像素 有些是宽屏 因此它们的高度略小于 240 像素 就目前情况而言 转换后的 FLV 的
  • 从视频或音频文件中删除人声

    有没有一种方法可以从音频 视频中删除人声 所以最终音乐就留在上面了 我想使用任何软件 如 adobe 等 或使用命令行 如 ffmpeg sox 来执行此操作 但我更喜欢命令行来轻松调整设置 我从事卡拉 OK 工作有一段时间了 没有办法可靠
  • ffmpeg流rc缓冲区下溢

    目前我正在使用开源工具 ffmpeg ffserver 建立一个屏幕共享平台 分享之初一切都很好 大约 1 1 2 分钟后 我在输出中得到以下异常 flv 0x3a47aa0 rc buffer underflow flv 0x3a47aa
  • 如何从 youtube-dl --write-auto-sub 下载转换混乱的 .vtt 子文件?

    我的目标是在单独的文件中下载带有自动生成字幕的 YouTube 视频 例如 vtt srt etc 我目前正在努力实现这一目标youtube dl但如果需要的话 我愿意接受其他解决方案 当我运行以下命令时 它将视频下载为 mp4 这很好 和
  • 将网络摄像头从浏览器流式传输到 RTMP 服务器

    我正在尝试将一些内容从浏览器的网络摄像头实现流式传输到随机 RTMP 服务器 我让它工作到每 2 秒将 WEBM 我相信是 VP8 编码的电影片段发送到我的服务器的部分 但棘手的部分是从该部分将其发送到 RTMP 服务器 对 FFMPEG
  • 使用 ffmpeg 将文件从一种格式转换为另一种格式

    我是新来的ffmpeg我试图找出如何将音频或视频文件从一种格式转换为另一种格式 我不想使用CLI 我只是想知道我是否可以使用ffmpeg作为库并调用函数将文件从一种格式转换为另一种格式 我浏览了文档并找到了函数avcodec encode
  • 来自 http 直播 m3u8 文件的 FFMPEG mp4? [关闭]

    Closed 这个问题不符合堆栈溢出指南 help closed questions 目前不接受答案 如何从 http 直播 m3u8 文件中提取 mp4 我尝试了下面这个命令 ffmpeg i input file f rawvideo
  • FFMPEG 没有按预期“切割”

    我通过一个简单的 system process 使用 FFMPEG 来自 java 应用程序 并尝试将视频切成块 我正在尝试将其切成 10 秒的增量 我的 FFMPEG 命令如下所示 ffmpeg i SampleVideo mp4 ss
  • OpenCV 3.0.0 使用 FFMPEG 时出错

    我使用 OpenCV 一段时间了 但是 我最近将系统更改为没有任何管理员权限的集群 问题是这样的 在我的主文件夹中 我安装了 FFMPEG ffmpeg 网站上提供的最新稳定版本 我将它安装在 HOME 中 因此在 HOME lib 中安装
  • 如何调试视频解码损坏?

    我刚刚开始为一家新公司工作 我的新角色要求我帮助调试他们通过解码帧接收到的视频损坏 尽管我打算深入研究代码并研究问题的具体细节 但它让我开始思考视频调试的总体情况 由于处理视频对我来说非常陌生 整个过程看起来相当复杂 而且似乎有很多地方可以
  • 在 FFmpeg 中使用 -filter_complex amerge 时混合流

    我目前遇到 ffmpeg 及其过滤器之一的问题 我正在尝试将视频的 2 个音频流合并为一个 为此我尝试了这个命令 ffmpeg i home maniaplanet Videos ManiaPlanet 2014 08 21 20 09 1
  • C# 从视频文件的一部分中提取帧

    使用 AForge ffmpeg 包装器 您可以使用 VideoFileReader 类从视频中提取帧并将其保存为位图 请参阅以下示例 提取 avi 文件的帧 https stackoverflow com questions 178256
  • 将每分钟的 MP3 导出为单独的 WAV

    这绝对是一个奇怪的问题 但我正在寻找一种方法 将 60 分钟的 mp3 混合拆分为 60 个单独的 1 分钟长的 wav 文件 以便与 Echonest 等音频指纹识别 API 一起使用 这是否可以在单个 ffmpeg 命令中实现 或者我是
  • 使用 ffmpeg 将 h.264 avi 容器转换为 mp4

    我想使用 ffmpeg 将 h 264 avi 容器转换为 mp4 容器 我发现这个有效 ffmpeg i myfile avi vcodec copy myfile mp4 ffmpeg version N 51169 gcedf276
  • 如何使用 ffmpeg 提取时间精确的视频片段?

    这并不是一个特别新的问题领域 但我已经尝试过那里建议的内容 但运气不佳 那么 我的故事 我有一大段 15 秒的直接来自camera mov 视频 我想从中提取特定的块 我可以通过开始时间和停止时间 以秒为单位 来识别该块 我首先尝试执行我称
  • C# - 捕获 RTP 流并发送到语音识别

    我正在努力实现的目标 在 C 中捕获 RTP 流 将该流转发到 System Speech SpeechRecognitionEngine 我正在创建一个基于 Linux 的机器人 它将接受麦克风输入 将其发送给 Windows 机器 Wi
  • 使用 ffmpeg 提取帧的最快方法?

    您好 我需要使用 ffmpeg 从视频中提取帧 有没有比这更快的方法 ffmpeg i file mpg r 1 1 filename 03d jpg 如果 JPEG 编码步骤对性能要求太高 您可以始终将未压缩的帧存储为 BMP 图像 ff
  • 如何在Android项目中使用libffmpeg.so?

    我正在尝试在 Android 中创建一个屏幕录制应用程序 为此 我使用 FFmpeg 我已经创建了 libffmpeg so 文件 现在我想在 Android 项目中使用相同的方法来调用它的本机函数 我怎样才能做到这一点 本教程提供了有关此
  • 如何使用android ndk r9b为Android编译FFMPEG

    我想设计一个Android应用程序 可以通过FFMPEG命令播放和编辑视频 但我不知道如何在Android上使用FFMPEG 我尝试过从Google搜索到的许多方法 但它们太旧了 无法实现 现在 FFMPEG的最新版本是2 1 1 Andr
  • Python 用静态图像将 mp3 转换为 mp4

    我有x文件包含一个列表mp3我想转换的文件mp3文件至mp4文件带有static png photo 似乎这里唯一的方法是使用ffmpeg但我不知道如何实现它 我编写了脚本来接受输入mp3文件夹和一个 png photo 然后它将创建新文件

随机推荐