【ffmpeg数据结构 3】AVPacket && AVFrame

256 阅读7分钟

3.3 内存模型

3.3.1 avpacket

属性作用
AVBufferRef *buf;
int64_t pts;显示时间戳,单位为 time_base 时间基
int64_t dts解码时间戳,单位为 time_base时间基
int64_t duration;视频帧的时长,单位 time_base时间基, 音频帧的samples数目
uint8_t *data;
int size
int stream_index该帧所属的 stream_id
int flags
int64_t pos当前packet在音视频文件中位置(偏移量)

AVPacket *pkt = NULL;

pkt = av_packet_alloc(); buf为0

ret = av_new_packet(pkt, MEM_ITEM_SIZE);初始化buf为1

av_packet_free(&pkt);释放所有内存,内调用av_packet_unref

av_init_packrt:将buf置为0,再次调用av_packet_free造成内存泄漏,不要乱用

av_packet_ref(pkt2, pkt): 是一个用于引用(引用计数)AVPacket 结构的函数。这个函数会增加 AVPacket 结构的引用计数,从而 允许多个地方引用同一个数据包而不复制其内容。这在并发处理和内存效率方面很有用

av_packet_unref(pkt): 用于取消引用(减少引用计数)AVPacket 结构的函数。这个函数会减少 AVPacket 结构的引用计数,如果引 用计数变为零,将释放相关的内存。

av_packet_move_ref:将buff拷贝给另一个,原来buff置为0,内部调用av_init_packet

 // av_read_frame 函数用于从音视频文件中读取一帧数据,但它并不关心这一帧是音频还是视频。
 // 该函数从文件中读取下一个压缩后的数据包(AVPacket),而文件中可能包含音频流和视频流,或者其他类型的流,取决于文件的内容。
 ​
 要区分音频和视频,你需要检查 AVPacket 中的 stream_index 字段,该字段表示数据包所属的流的索引。在 AVFormatContext 结构体的 streams 数组中,通过 stream_index 可以获取到对应的流。
 int av_read_frame(AVFormatContext *s, AVPacket *pkt);
 ​
 AVPacket *pkt = av_packet_alloc();
 ret = av_read_frame(ifmt_ctx, pkt);

3.3.2 avframe

1 编码音频设置

 AVFrame frame = av_frame_alloc();
 if (!frame)
 {
     fprintf(stderr, "Could not allocate audio frame\n");
     exit(1);
 }
 /* 每次送多少数据给编码器由:
      *  (1)frame_size(每帧单个通道的采样点数);
      *  (2)sample_fmt(采样点格式);
      *  (3)channel_layout(通道布局情况);
      * 3要素决定
      */
 ​
 // 设置输入音频帧参数
 frame->nb_samples     = codec_ctx->frame_size; // 帧大小
 frame->format         = codec_ctx->sample_fmt;
 frame->channel_layout = codec_ctx->channel_layout;
 frame->channels = av_get_channel_layout_nb_channels(frame->channel_layout);
 printf("frame nb_samples:%d\n", frame->nb_samples);
 printf("frame sample_fmt:%d\n", frame->format);
 printf("frame channel_layout:%lu\n\n", frame->channel_layout);
 /* 为frame分配buffer */
 ​
 // 分配输出缓冲区
 ret = av_frame_get_buffer(frame, 0);
 if (ret < 0)
 {
     fprintf(stderr, "Could not allocate audio data buffers\n");
     exit(1);
 }
 ​
 // infile.pcm  ->  buffer  -> frame
 // 每次传送1帧数据,帧大小 = 样本数 x 样本大小 x 通道数
 int frame_bytes = av_get_bytes_per_sample(frame->format) \
             * frame->channels \
             * frame->nb_samples;
 ​
 // 将读取到的PCM数据填充到frame去,但要注意格式的匹配, 是planar还是packed都要区分清楚
 // 将本地的f32le packed模式的数据转为float palanar
 memset(pcm_temp_buf, 0, frame_bytes);
 f32le_convert_to_fltp((float *)pcm_buf, (float *)pcm_temp_buf, frame->nb_samples);
 ret = av_samples_fill_arrays(frame->data, frame->linesize,
                              pcm_temp_buf, frame->channels,
                              frame->nb_samples, frame->format, 0);
 ​

2 编码视频设置

 AVFrame frame = av_frame_alloc();
 if (!frame) {
     fprintf(stderr, "Could not allocate video frame\n");
     exit(1);
 }
 ​
 // 为frame分配buffer
 frame->format = codec_ctx->pix_fmt;
 frame->width  = codec_ctx->width;
 frame->height = codec_ctx->height;
 ret = av_frame_get_buffer(frame, 0);
 if (ret < 0) {
     fprintf(stderr, "Could not allocate the video frame data\n");
     exit(1);
 }
 // 计算出每一帧的数据 像素格式 * 宽 * 高
 // 1382400
 int frame_bytes = av_image_get_buffer_size(frame->format, frame->width,
                                            frame->height, 1);
 printf("frame_bytes %d\n", frame_bytes);
 uint8_t *yuv_buf = (uint8_t *)malloc(frame_bytes);
 if(!yuv_buf) {
     printf("yuv_buf malloc failed\n");
     return 1;
 }

3.3.3 send、reveive

解码API

关于 avcodec_send_packet() 与 avcodec_receive_frame() 的使用说明:

[1] 按 dts 递增的顺序向解码器送入编码帧 packet,解码器按 pts 递增的顺序输出原始帧 frame,实际上解码器不关注输入 packe t的 dts(错值都没关系),它只管依次处理收到的 packet,按需缓冲和解码

[2] avcodec_receive_frame() 输出 frame 时,会根据各种因素设置好 frame->best_effort_timestamp(文档明确说明),实测 frame->pts 也会被设置(通常直接拷贝自对应的 packet.pts,文档未明确说明)用户应确保 avcodec_send_packet() 发送的 packet 具有正确的 pts,编码帧 packet 与原始帧 frame 间的对应关系通过 pts 确定

[3] avcodec_receive_frame() 输出 frame 时,frame->pkt_dts 拷贝自当前avcodec_send_packet() 发送的 packet 中的 dts,如果当前 packet 为 NULL(flush packet),解码器进入 flush 模式,当前及剩余的 frame->pkt_dts 值总为 AV_NOPTS_VALUE。因为解码器中有缓存帧,当前输出的 frame 并不是由当前输入的 packet 解码得到的,所以这个 frame->pkt_dts 没什么实际意义,可以不必关注

[4] avcodec_send_packet() 发送第一个 NULL 会返回成功,后续的 NULL 会返回 AVERROR_EOF

[5] avcodec_send_packet() 多次发送 NULL 并不会导致解码器中缓存的帧丢失,使用 avcodec_flush_buffers() 可以立即丢掉解码器中缓存帧。因此播放完毕时应 avcodec_send_packet(NULL) 来取完缓存的帧,而 SEEK 操作或切换流时应调用 avcodec_flush_buffers() 来直接丢弃缓存帧

[6] 解码器通常的冲洗方法:调用一次 avcodec_send_packet(NULL)(返回成功),然后不停调用 avcodec_receive_frame() 直到其返回 AVERROR_EOF,取出所有缓存帧,avcodec_receive_frame() 返回 AVERROR_EOF 这一次是没有有效数据的,仅仅获取到一个结束标志

编码API

关于 avcodec_send_frame() 与 avcodec_receive_packet() 的使用说明:

 @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 decoding errors
  */
 int avcodec_send_frame(AVCodecContext *avctx, const AVFrame *frame);
 @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 an encoder
  *      other errors: legitimate decoding errors
  */
 int avcodec_receive_packet(AVCodecContext *avctx, AVPacket *avpkt);

pts 递增的顺序向编码器送入原始帧 frame,编码器按 dts 递增的顺序输出编码帧 packet,实际上编码器关注输入 frame 的 pts 不关注其 dts,它只管依次处理收到的 frame,按需缓冲和编码

avcodec_receive_packet() 输出 packet 时,会设置 packet.dts,从 0 开始,每次输出的 packet 的 dts 加 1,这是视频层的 dts,用户写输出前应将其转换为容器层的 dts

avcodec_receive_packet() 输出 packet 时,packet.pts 拷贝自对应的 frame.pts,这是视频层的 pts,用户写输出前应将其转换为容器层的 pts

avcodec_send_frame() 发送NULLframe 时,编码器进入 flush 模式

avcodec_send_frame() 发送第一个 NULL 会返回成功,后续的 NULL 会返回 AVERROR_EOF

avcodec_send_frame() 多次发送 NULL 并不会导致编码器中缓存的帧丢失,使用 avcodec_flush_buffers() 可以立即丢掉编码器中缓存帧。因此编码完毕时应使用 avcodec_send_frame(NULL) 来取完缓存的帧,而SEEK操作或切换流时应调用 avcodec_flush_buffers() 来直接丢弃缓存帧

编码器通常的冲洗方法:调用一次 avcodec_send_frame(NULL)(返回成功),然后不停调用 avcodec_receive_packet() 直到其返回 AVERROR_EOF,取出所有缓存帧,avcodec_receive_packet() 返回 AVERROR_EOF 这一次是没有有效数据的,仅仅获取到一个结束标志

对音频来说,如果 AV_CODEC_CAP_VARIABLE_FRAME_SIZE(在 AVCodecContext.codec.capabilities 变量中,只读)标志有效,表示编码器支持可变尺寸音频帧,送入编码器的音频帧可以包含任意数量的采样点。如果此标志无效,则每一个音频帧的采样点数目(frame->nb_samples)必须等于编码器设定的音频帧尺寸(avctx->frame_size),最后一帧除外,最后一帧音频帧采样点数可以小于 avctx->frame_size