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