【FFmpeg】avcodec_send_packet函数

news/2024/10/5 23:00:04/

目录

  • 1.avcodec_send_packet
    • 1.1 解码入口函数(decode_receive_frame_internal)
      • 1.1.1 软解入口(decode_simple_receive_frame)

FFmpeg相关记录:

示例工程:
【FFmpeg】调用ffmpeg库实现264软编
【FFmpeg】调用ffmpeg库实现264软解
【FFmpeg】调用ffmpeg库进行RTMP推流和拉流
【FFmpeg】调用ffmpeg库进行SDL2解码后渲染

流程分析:
【FFmpeg】编码链路上主要函数的简单分析
【FFmpeg】解码链路上主要函数的简单分析

结构体分析:
【FFmpeg】AVCodec结构体
【FFmpeg】AVCodecContext结构体
【FFmpeg】AVStream结构体
【FFmpeg】AVFormatContext结构体
【FFmpeg】AVIOContext结构体
【FFmpeg】AVPacket结构体

函数分析:
【通用】
【FFmpeg】avcodec_find_encoder和avcodec_find_decoder
【FFmpeg】关键结构体的初始化和释放(AVFormatContext、AVIOContext等)
【FFmpeg】avcodec_open2函数

【推流】
【FFmpeg】avformat_open_input函数
【FFmpeg】avformat_find_stream_info函数
【FFmpeg】avformat_alloc_output_context2函数
【FFmpeg】avio_open2函数
【FFmpeg】avformat_write_header函数
【FFmpeg】av_write_frame函数

【编码】
【FFmpeg】avcodec_send_frame函数

1.avcodec_send_packet

函数的主要功能是将一个压缩包送入到解码器当中进行解码,定义位于libavcodec\decode.c中

/*** Supply raw packet data as input to a decoder.** Internally, this call will copy relevant AVCodecContext fields, which can* influence decoding per-packet, and apply them when the packet is actually* decoded. (For example AVCodecContext.skip_frame, which might direct the* decoder to drop the frame contained by the packet sent with this function.)** @warning The input buffer, avpkt->data must be AV_INPUT_BUFFER_PADDING_SIZE*          larger than the actual read bytes because some optimized bitstream*          readers read 32 or 64 bits at once and could read over the end.** @note The AVCodecContext MUST have been opened with @ref avcodec_open2()*       before packets may be fed to the decoder.** @param avctx codec context* @param[in] avpkt The input AVPacket. Usually, this will be a single video*                  frame, or several complete audio frames.*                  Ownership of the packet remains with the caller, and the*                  decoder will not write to the packet. The decoder may create*                  a reference to the packet data (or copy it if the packet is*                  not reference-counted).*                  Unlike with older APIs, the packet is always fully consumed,*                  and if it contains multiple frames (e.g. some audio codecs),*                  will require you to call avcodec_receive_frame() multiple*                  times afterwards before you can send a new packet.*                  It can be NULL (or an AVPacket with data set to NULL and*                  size set to 0); in this case, it is considered a flush*                  packet, which signals the end of the stream. Sending the*                  first flush packet will return success. Subsequent ones are*                  unnecessary and will return AVERROR_EOF. If the decoder*                  still has frames buffered, it will return them after sending*                  a flush packet.** @retval 0                 success* @retval AVERROR(EAGAIN)   input is not accepted in the current state - user*                           must read output with avcodec_receive_frame() (once*                           all output is read, the packet should be resent,*                           and the call will not fail with EAGAIN).* @retval AVERROR_EOF       the decoder has been flushed, and no new packets can be*                           sent to it (also returned if more than 1 flush*                           packet is sent)* @retval AVERROR(EINVAL)   codec not opened, it is an encoder, or requires flush* @retval AVERROR(ENOMEM)   failed to add packet to internal queue, or similar* @retval "another negative error code" legitimate decoding errors*/
// 提供原始数据包数据作为解码器的输入
int attribute_align_arg avcodec_send_packet(AVCodecContext *avctx, const AVPacket *avpkt)
{AVCodecInternal *avci = avctx->internal;DecodeContext     *dc = decode_ctx(avci);int ret;// 1.输入信息的检查if (!avcodec_is_open(avctx) || !av_codec_is_decoder(avctx->codec))return AVERROR(EINVAL);if (dc->draining_started)return AVERROR_EOF;if (avpkt && !avpkt->size && avpkt->data)return AVERROR(EINVAL);if (avpkt && (avpkt->data || avpkt->side_data_elems)) {if (!AVPACKET_IS_EMPTY(avci->buffer_pkt))return AVERROR(EAGAIN);ret = av_packet_ref(avci->buffer_pkt, avpkt);if (ret < 0)return ret;} elsedc->draining_started = 1;// 2.解码入口if (!avci->buffer_frame->buf[0] && !dc->draining_started) {ret = decode_receive_frame_internal(avctx, avci->buffer_frame);if (ret < 0 && ret != AVERROR(EAGAIN) && ret != AVERROR_EOF)return ret;}return 0;
}

1.1 解码入口函数(decode_receive_frame_internal)

函数的定义位于libavcodec\decode.c中,如下所示。这里最重要的地方在于检查cb_type,如果使用硬件解码器,则使用receive_frame,否则使用decode_simple_receive_frame执行软解过程

static int decode_receive_frame_internal(AVCodecContext *avctx, AVFrame *frame)
{AVCodecInternal *avci = avctx->internal;DecodeContext     *dc = decode_ctx(avci);const FFCodec *const codec = ffcodec(avctx->codec);int ret, ok;av_assert0(!frame->buf[0]);// 如果使用硬件解码器,cb_type才会是FF_CODEC_CB_TYPE_RECEIVE_FRAME// 这里使用函数指针,是调用硬件的解码器if (codec->cb_type == FF_CODEC_CB_TYPE_RECEIVE_FRAME) {ret = codec->cb.receive_frame(avctx, frame);emms_c();if (!ret) {if (avctx->codec->type == AVMEDIA_TYPE_VIDEO)ret = (frame->flags & AV_FRAME_FLAG_DISCARD) ? AVERROR(EAGAIN) : 0;else if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {int64_t discarded_samples = 0;ret = discard_samples(avctx, frame, &discarded_samples);}}} else // 使用软件解码器ret = decode_simple_receive_frame(avctx, frame);if (ret == AVERROR_EOF)avci->draining_done = 1;/* preserve ret */// 检测colorspace,这里函数还没实现,默认返回为0ok = detect_colorspace(avctx, frame);if (ok < 0) {av_frame_unref(frame);return ok;}if (!ret) {if (avctx->codec_type == AVMEDIA_TYPE_VIDEO) {if (!frame->width)frame->width = avctx->width;if (!frame->height)frame->height = avctx->height;} elseframe->flags |= AV_FRAME_FLAG_KEY;ret = fill_frame_props(avctx, frame);if (ret < 0) {av_frame_unref(frame);return ret;}#if FF_API_FRAME_KEY
FF_DISABLE_DEPRECATION_WARNINGSframe->key_frame = !!(frame->flags & AV_FRAME_FLAG_KEY); // 配置key_frame
FF_ENABLE_DEPRECATION_WARNINGS
#endif
#if FF_API_INTERLACED_FRAME
FF_DISABLE_DEPRECATION_WARNINGSframe->interlaced_frame = !!(frame->flags & AV_FRAME_FLAG_INTERLACED);frame->top_field_first =  !!(frame->flags & AV_FRAME_FLAG_TOP_FIELD_FIRST);
FF_ENABLE_DEPRECATION_WARNINGS
#endifframe->best_effort_timestamp = guess_correct_pts(dc,frame->pts,frame->pkt_dts);/* the only case where decode data is not set should be decoders* that do not call ff_get_buffer() */// 不设置decode数据的唯一情况应该是解码器不调用ff_get_buffer()av_assert0((frame->private_ref && frame->private_ref->size == sizeof(FrameDecodeData)) ||!(avctx->codec->capabilities & AV_CODEC_CAP_DR1));if (frame->private_ref) {FrameDecodeData *fdd = (FrameDecodeData*)frame->private_ref->data;if (fdd->post_process) {ret = fdd->post_process(avctx, frame);if (ret < 0) {av_frame_unref(frame);return ret;}}}}/* free the per-frame decode data */av_buffer_unref(&frame->private_ref);return ret;
}

1.1.1 软解入口(decode_simple_receive_frame)

static int decode_simple_receive_frame(AVCodecContext *avctx, AVFrame *frame)
{int ret;int64_t discarded_samples = 0;while (!frame->buf[0]) {if (discarded_samples > avctx->max_samples)return AVERROR(EAGAIN);ret = decode_simple_internal(avctx, frame, &discarded_samples);if (ret < 0)return ret;}return 0;
}

函数最重要的是调用了decode_simple_internal,函数的定义如下

/** The core of the receive_frame_wrapper for the decoders implementing* the simple API. Certain decoders might consume partial packets without* returning any output, so this function needs to be called in a loop until it* returns EAGAIN.**/
// 用于实现简单API的解码器的receive_frame_wrapper的核心。某些解码器可能会消耗部分数据包而不返回任何输出
// 因此需要在循环中调用此函数,直到它返回EAGAIN
static inline int decode_simple_internal(AVCodecContext *avctx, AVFrame *frame, int64_t *discarded_samples)
{AVCodecInternal   *avci = avctx->internal;AVPacket     *const pkt = avci->in_pkt;const FFCodec *const codec = ffcodec(avctx->codec);int got_frame, consumed;int ret;// 如果pkt没有data,获取下一个pktif (!pkt->data && !avci->draining) {av_packet_unref(pkt);ret = ff_decode_get_packet(avctx, pkt);if (ret < 0 && ret != AVERROR_EOF)return ret;}// Some codecs (at least wma lossless) will crash when feeding drain packets// after EOF was signaled.// 一些编解码器(至少是wma无损的)在EOF信号发出后输入漏包时会崩溃if (avci->draining_done)return AVERROR_EOF;if (!pkt->data &&!(avctx->codec->capabilities & AV_CODEC_CAP_DELAY ||avctx->active_thread_type & FF_THREAD_FRAME))return AVERROR_EOF;got_frame = 0;// 执行解码过程if (HAVE_THREADS && avctx->active_thread_type & FF_THREAD_FRAME) {// 线程解码consumed = ff_thread_decode_frame(avctx, frame, &got_frame, pkt);} else {// 根据使用的解码器进行解码consumed = codec->cb.decode(avctx, frame, &got_frame, pkt);if (!(codec->caps_internal & FF_CODEC_CAP_SETS_PKT_DTS))frame->pkt_dts = pkt->dts;if (avctx->codec->type == AVMEDIA_TYPE_VIDEO) {
#if FF_API_FRAME_PKT
FF_DISABLE_DEPRECATION_WARNINGSif(!avctx->has_b_frames)frame->pkt_pos = pkt->pos;
FF_ENABLE_DEPRECATION_WARNINGS
#endif}}emms_c();if (avctx->codec->type == AVMEDIA_TYPE_VIDEO) {ret = (!got_frame || frame->flags & AV_FRAME_FLAG_DISCARD)? AVERROR(EAGAIN): 0;} else if (avctx->codec->type == AVMEDIA_TYPE_AUDIO) {ret =  !got_frame ? AVERROR(EAGAIN): discard_samples(avctx, frame, discarded_samples);}if (ret == AVERROR(EAGAIN))av_frame_unref(frame);// FF_CODEC_CB_TYPE_DECODE decoders must not return AVERROR EAGAIN// code later will add AVERROR(EAGAIN) to a pointerav_assert0(consumed != AVERROR(EAGAIN));if (consumed < 0)ret = consumed;if (consumed >= 0 && avctx->codec->type == AVMEDIA_TYPE_VIDEO)consumed = pkt->size;if (!ret)av_assert0(frame->buf[0]);if (ret == AVERROR(EAGAIN))ret = 0;/* do not stop draining when got_frame != 0 or ret < 0 */if (avci->draining && !got_frame) {if (ret < 0) {/* prevent infinite loop if a decoder wrongly always return error on draining *//* reasonable nb_errors_max = maximum b frames + thread count */int nb_errors_max = 20 + (HAVE_THREADS && avctx->active_thread_type & FF_THREAD_FRAME ?avctx->thread_count : 1);if (decode_ctx(avci)->nb_draining_errors++ >= nb_errors_max) {av_log(avctx, AV_LOG_ERROR, "Too many errors when draining, this is a bug. ""Stop draining and force EOF.\n");avci->draining_done = 1;ret = AVERROR_BUG;}} else {avci->draining_done = 1;}}if (consumed >= pkt->size || ret < 0) {av_packet_unref(pkt);} else {pkt->data                += consumed;pkt->size                -= consumed;pkt->pts                  = AV_NOPTS_VALUE;pkt->dts                  = AV_NOPTS_VALUE;if (!(codec->caps_internal & FF_CODEC_CAP_SETS_FRAME_PROPS)) {
#if FF_API_FRAME_PKT// See extract_packet_props() comment.avci->last_pkt_props->stream_index = avci->last_pkt_props->stream_index - consumed;
#endifavci->last_pkt_props->pts = AV_NOPTS_VALUE;avci->last_pkt_props->dts = AV_NOPTS_VALUE;}}return ret;
}

代码最核心的地方在于调用codec->cb.decode执行解码过程,在FFmpeg中内嵌了H264的解码器,以这个为例进行记录,会使用h264_decode_frame进行解码

const FFCodec ff_h264_decoder = {.p.name                = "h264",CODEC_LONG_NAME("H.264 / AVC / MPEG-4 AVC / MPEG-4 part 10"),.p.type                = AVMEDIA_TYPE_VIDEO,.p.id                  = AV_CODEC_ID_H264,.priv_data_size        = sizeof(H264Context),.init                  = h264_decode_init,.close                 = h264_decode_end,FF_CODEC_DECODE_CB(h264_decode_frame), // 使用h264_decode_frame进行解码.p.capabilities        = AV_CODEC_CAP_DR1 |AV_CODEC_CAP_DELAY | AV_CODEC_CAP_SLICE_THREADS |AV_CODEC_CAP_FRAME_THREADS,.hw_configs            = (const AVCodecHWConfigInternal *const []) {// ...NULL},.caps_internal         = FF_CODEC_CAP_EXPORTS_CROPPING |FF_CODEC_CAP_ALLOCATE_PROGRESS | FF_CODEC_CAP_INIT_CLEANUP,.flush                 = h264_decode_flush,UPDATE_THREAD_CONTEXT(ff_h264_update_thread_context),UPDATE_THREAD_CONTEXT_FOR_USER(ff_h264_update_thread_context_for_user),.p.profiles            = NULL_IF_CONFIG_SMALL(ff_h264_profiles),.p.priv_class          = &h264_class,
};

h264_decode_frame的定义位于libavcodec\h264dec.c中,定义如下

static int h264_decode_frame(AVCodecContext *avctx, AVFrame *pict,int *got_frame, AVPacket *avpkt)
{const uint8_t *buf = avpkt->data;int buf_size       = avpkt->size;H264Context *h     = avctx->priv_data;int buf_index;int ret;h->flags = avctx->flags;h->setup_finished = 0;h->nb_slice_ctx_queued = 0;ff_h264_unref_picture(&h->last_pic_for_ec);/* end of stream, output what is still in the buffers */// 到达流的末尾,输出buffer当中现有的数据if (buf_size == 0)return send_next_delayed_frame(h, pict, got_frame, 0);// 解码side dataif (av_packet_get_side_data(avpkt, AV_PKT_DATA_NEW_EXTRADATA, NULL)) {size_t side_size;uint8_t *side = av_packet_get_side_data(avpkt, AV_PKT_DATA_NEW_EXTRADATA, &side_size);ff_h264_decode_extradata(side, side_size,&h->ps, &h->is_avc, &h->nal_length_size,avctx->err_recognition, avctx);}// 解码extra dataif (h->is_avc && buf_size >= 9 && buf[0]==1 && buf[2]==0 && (buf[4]&0xFC)==0xFC) {if (is_avcc_extradata(buf, buf_size))return ff_h264_decode_extradata(buf, buf_size,&h->ps, &h->is_avc, &h->nal_length_size,avctx->err_recognition, avctx);}// 解码入口buf_index = decode_nal_units(h, buf, buf_size);if (buf_index < 0)return AVERROR_INVALIDDATA;// 到达文件末尾if (!h->cur_pic_ptr && h->nal_unit_type == H264_NAL_END_SEQUENCE) {av_assert0(buf_index <= buf_size);return send_next_delayed_frame(h, pict, got_frame, buf_index);}if (!(avctx->flags2 & AV_CODEC_FLAG2_CHUNKS) && (!h->cur_pic_ptr || !h->has_slice)) {if (avctx->skip_frame >= AVDISCARD_NONREF ||buf_size >= 4 && !memcmp("Q264", buf, 4))return buf_size;av_log(avctx, AV_LOG_ERROR, "no frame!\n");return AVERROR_INVALIDDATA;}if (!(avctx->flags2 & AV_CODEC_FLAG2_CHUNKS) ||(h->mb_y >= h->mb_height && h->mb_height)) {if ((ret = ff_h264_field_end(h, &h->slice_ctx[0], 0)) < 0)return ret;/* Wait for second field. */if (h->next_output_pic) {ret = finalize_frame(h, pict, h->next_output_pic, got_frame);if (ret < 0)return ret;}}av_assert0(pict->buf[0] || !*got_frame);ff_h264_unref_picture(&h->last_pic_for_ec);// 获取使用的比特数return get_consumed_bytes(buf_index, buf_size);
}

在上面的代码中,先检查是否需要解码side data和extra data,随后使用decode_nal_units进行数据包的解码,随后会进入H264格式的解码过程,这个过程链路比较长,就是另一个系列记录的内容了,这里不做进一步记录

CSDN : https://blog.csdn.net/weixin_42877471
Github : https://github.com/DoFulangChen


http://www.ppmy.cn/news/1473737.html

相关文章

Python爬虫零基础实战,简洁实用!

1.爬虫简介 简单来讲&#xff0c;爬虫就是一个探测机器&#xff0c;它的基本操作就是模拟人的行为去各个网站溜达&#xff0c;点点按钮&#xff0c;查查数据&#xff0c;或者把看到的信息背回来。就像一只虫子在一幢楼里不知疲倦地爬来爬去。 你可以简单地想象&#xff1a;每个…

FPGA_GTX:简要版

1. GTX介绍 Xilinx FPGA的GT意思是Gigabyte Transceiver。通常称呼为Serdes、高速收发器。GT在xilinx不同系列有着不同的产品&#xff0c;从7系列到UltraScale系列分别有GTP、GTX、GTZ、GTH、GTY和GTM。不同GT整体结构上类似&#xff0c;为了支持越来越高的line rate&#xff…

SCI论文发表:构建清晰论文框架的10个原则 (附思维导图,建议收藏)

我是娜姐 迪娜学姐 &#xff0c;一个SCI医学期刊编辑&#xff0c;探索用AI工具提效论文写作和发表。 论文框架是什么&#xff1f;对我们完成一篇论文有哪些作用&#xff1f; 之前娜姐分享过一篇深圳湾实验室周耀旗教授关于论文写作的文章&#xff0c;他提出的第一个重要原则就…

安全测试之使用Docker搭建SQL注入安全测试平台sqli-labs

1 搜索镜像 docker search sqli-labs 2 拉取镜像 docker pull acgpiano/sqli-labs 3 创建docker容器 docker run -d --name sqli-labs -p 10012:80 acgpiano/sqli-labs 4 访问测试平台网站 若直接使用虚拟机&#xff0c;则直接通过ip端口号访问若通过配置域名&#xff0…

进程控制-exec函数

让父子进程来执行不相干的操作 能够替换进程地址空间的代码.text段 执行另外的程序&#xff0c;不需要创建额外的的地址空间 当前程序中调用另外一个应用程序 指定执行目录下的程序 int execl(const char *path, const char *arg&#xff0c;/* (char *) NULL */); /* pat…

【国产开源可视化引擎Meta2d.js】拖拽

Meta2d.js已监听拖拽事件&#xff0c;支持接收一个有效的图元Json数据&#xff0c;在画布创建一个图元对象。 图形库拖拽 1. 创建图形库工具栏 创建图形库工具栏 html 元素&#xff0c;绑定拖拽事件或 touch 事件 <div v-for"item in list" draggable"tr…

静态时序分析:ideal_clock、propagated_clock以及generated_clock的关系及其延迟计算规则(二)

相关阅读 静态时序分析https://blog.csdn.net/weixin_45791458/category_12567571.html?spm1001.2014.3001.5482 生成时钟 上一节中&#xff0c;我们讨论了理想时钟和传播时钟的创建和使用&#xff0c;本节将讨论生成时钟及其与理想时钟和传播时钟的关系。 图1所示的是一个简…

从零开始使用WordPress搭建个人网站并一键发布公网详细教程

文章目录 前言1. 搭建网站&#xff1a;安装WordPress2. 搭建网站&#xff1a;创建WordPress数据库3. 搭建网站&#xff1a;安装相对URL插件4. 搭建网站&#xff1a;内网穿透发布网站4.1 命令行方式&#xff1a;4.2. 配置wordpress公网地址 5. 固定WordPress公网地址5.1. 固定地…