AVFormatContext编解码层:理论与实战

2023-12-10 11:04

本文主要是介绍AVFormatContext编解码层:理论与实战,希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

文章目录

  • 前言
  • 一、FFmpeg 解码流程
  • 二、FFmpeg 转码流程
  • 三、编解码 API 详解
    • 1、解码 API 使用详解
    • 2、编码 API 使用详解
  • 四、编码案例实战
    • 1、示例源码
    • 2、运行结果
  • 五、解码案例实战
    • 1、示例源码
    • 2、运行结果


前言

AVFormatContext 是一个贯穿始终的数据结构,很多函数都用到它作为参数,是输入输出相关信息的一个容器,本文讲解 AVFormatContext 的编解码层,主要包括三大数据结构:AVStreamAVCodecContexAVCodec
在这里插入图片描述


一、FFmpeg 解码流程

在这里插入图片描述
得到输入文件 -> 解封格式 -> 得到编码的数据包 -> 解码数据包 -> 得到解码后的数据帧 -> 处理数据帧 -> 编码 -> 得到编码后的数据包 -> 封装格式 -> 输出文件

涉及到下面的 API 函数:

  • 注册所有容器格式和 CODEC:av_register_all()
  • 打开文件:av_open_input_file()
  • 从文件中提取流信息:av_find_stream_info()
  • 穷举所有的流,查找其中种类为 CODEC_TYPE_VIDEO
  • 查找对应的解码器:avcodec_find_decoder()
  • 打开编解码器:avcodec_open()
  • 为解码帧分配内存:avcodec_alloc_frame()
  • 不停地从码流中提取出帧数据:av_read_frame()
  • 判断帧的类型,对于视频帧调用:avcodec_decode_video()
  • 解码完后,释放解码器:avcodec_close()
  • 关闭输入文件:av_close_input_file()

程序流程图如下图所示:
在这里插入图片描述

二、FFmpeg 转码流程

在这里插入图片描述

  • 大流程可以划分为输入、输出、转码、播放四大块;
  • 其中转码涉及比较多的处理环节,从图中可以看出,转码功能在整个功能图中占比很大。转码的核心功能在解码和编码两个部分,但在一个可用的示例程序中,编码解码与输入输出是难以分割的。
  • 解复用器为解码器提供输入,解码器会输出原始帧,对原始帧可进行各种复杂的滤镜处理,滤镜处理后的帧经编码器生成编码帧,多路流的编码帧经复用器输出到输出文件。

三、编解码 API 详解

  • 解码使用 avcodec_send_packet()avcodec_receive_frame() 两个函数。
  • 编码使用 avcodec_send_frame()avcodec_receive_packet() 两个函数。

1、解码 API 使用详解

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

  • ①、按 dts 递增的顺序向解码器送入编码帧 packet,解码器按 pts 递增的顺序输出原始帧 frame,实际上解码器不关注输入 packet 的 dts(错值都没关系),它只管依次处理收到的 packet,按需缓冲和解码;
  • ②、avcodec_receive_frame() 输出 frame 时,会根据各种因素设置好 frame->best_effort_timestamp(文档明确说明),实测 frame->pts 也会被设置(通常直接拷贝自对应的 packet.pts,文档未明确说明)用户应确保 avcodec_send_packet() 发送的 packet 具有正确的 pts,编码帧 packet 与原始帧 frame 间的对应关系通过 pts 确定;
  • ③、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 没什么实际意义,可以不必关注;
  • ④、avcodec_send_packet() 发送第一个 NULL 会返回成功,后续的 NULL 会返回 AVERROR_EOF
  • ⑤、avcodec_send_packet() 多次发送 NULL 并不会导致解码器中缓存的帧丢失,使用 avcodec_flush_buffers() 可以立即丢掉解码器中缓存帧。因此播放完毕时应 avcodec_send_packet(NULL) 来取完缓存的帧,而 SEEK 操作或切换流时应调用 avcodec_flush_buffers() 来直接丢弃缓存帧;
  • ⑥、解码器通常的冲洗方法:调用一次 avcodec_send_packet(NULL)(返回成功),然后不停调用 avcodec_receive_frame() 直到其返回 AVERROR_EOF,取出所有缓存帧,avcodec_receive_frame() 返回 AVERROR_EOF 这一次是没有有效数据的,仅仅获取到一个结束标志;

2、编码 API 使用详解

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

  • ①、按 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() 发送 NULL frame 时, 编码器进入 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

四、编码案例实战

下面代码使用实现了一个简单的视频编码器,将指定的图像序列编码为 MPEG 视频文件

1、示例源码

#include <stdio.h>
#include <stdlib.h>
#include <string.h>#include <libavcodec/avcodec.h>#include <libavutil/opt.h>
#include <libavutil/imgutils.h>static void encode(AVCodecContext *enc_ctx, AVFrame *frame, AVPacket *pkt,FILE *outfile)
{int ret;/* send the frame to the encoder */if (frame)printf("Send frame %3"PRId64"\n", frame->pts);ret = avcodec_send_frame(enc_ctx, frame);if (ret < 0) {fprintf(stderr, "Error sending a frame for encoding\n");exit(1);}while (ret >= 0) {ret = avcodec_receive_packet(enc_ctx, pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF)return;else if (ret < 0) {fprintf(stderr, "Error during encoding\n");exit(1);}printf("Write packet %3"PRId64" (size=%5d)\n", pkt->pts, pkt->size);fwrite(pkt->data, 1, pkt->size, outfile);av_packet_unref(pkt);}
}int main(int argc, char **argv)
{const char *filename, *codec_name;const AVCodec *codec;AVCodecContext *c= NULL;int i, ret, x, y;FILE *f;AVFrame *frame;AVPacket *pkt;uint8_t endcode[] = { 0, 0, 1, 0xb7 };filename = "./debug/test666.mp4";codec_name = "mpeg1video";/* find the mpeg1video encoder */codec = avcodec_find_encoder_by_name(codec_name);if (!codec) {fprintf(stderr, "Codec '%s' not found\n", codec_name);exit(1);}c = avcodec_alloc_context3(codec);if (!c) {fprintf(stderr, "Could not allocate video codec context\n");exit(1);}pkt = av_packet_alloc();if (!pkt)exit(1);/* put sample parameters */c->bit_rate = 400000;/* resolution must be a multiple of two */c->width = 352;c->height = 288;/* frames per second */c->time_base = (AVRational){1, 25};c->framerate = (AVRational){25, 1};/* emit one intra frame every ten frames* check frame pict_type before passing frame* to encoder, if frame->pict_type is AV_PICTURE_TYPE_I* then gop_size is ignored and the output of encoder* will always be I frame irrespective to gop_size*/c->gop_size = 10;c->max_b_frames = 1;c->pix_fmt = AV_PIX_FMT_YUV420P;if (codec->id == AV_CODEC_ID_H264)av_opt_set(c->priv_data, "preset", "slow", 0);/* open it */ret = avcodec_open2(c, codec, NULL);if (ret < 0) {fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));exit(1);}f = fopen(filename, "wb");if (!f) {fprintf(stderr, "Could not open %s\n", filename);exit(1);}frame = av_frame_alloc();if (!frame) {fprintf(stderr, "Could not allocate video frame\n");exit(1);}frame->format = c->pix_fmt;frame->width  = c->width;frame->height = c->height;ret = av_frame_get_buffer(frame, 0);if (ret < 0) {fprintf(stderr, "Could not allocate the video frame data\n");exit(1);}/* encode 1 second of video */for (i = 0; i < 25; i++) {fflush(stdout);/* make sure the frame data is writable */ret = av_frame_make_writable(frame);if (ret < 0)exit(1);/* prepare a dummy image *//* Y */for (y = 0; y < c->height; y++) {for (x = 0; x < c->width; x++) {frame->data[0][y * frame->linesize[0] + x] = x + y + i * 3;}}/* Cb and Cr */for (y = 0; y < c->height/2; y++) {for (x = 0; x < c->width/2; x++) {frame->data[1][y * frame->linesize[1] + x] = 128 + y + i * 2;frame->data[2][y * frame->linesize[2] + x] = 64 + x + i * 5;}}frame->pts = i;/* encode the image */encode(c, frame, pkt, f);}/* flush the encoder */encode(c, NULL, pkt, f);/* add sequence end code to have a real MPEG file */if (codec->id == AV_CODEC_ID_MPEG1VIDEO || codec->id == AV_CODEC_ID_MPEG2VIDEO)fwrite(endcode, 1, sizeof(endcode), f);fclose(f);avcodec_free_context(&c);av_frame_free(&frame);av_packet_free(&pkt);return 0;
}

2、运行结果

Send frame   0
Send frame   1
Write packet   0 (size= 6731)
Send frame   2
Write packet   2 (size= 3727)
Send frame   3
Write packet   1 (size= 1680)
Send frame   4
Write packet   4 (size= 2744)
Send frame   5
Write packet   3 (size= 1678)
Send frame   6
Write packet   6 (size= 2963)
Send frame   7
Write packet   5 (size= 1819)
Send frame   8
Write packet   8 (size= 3194)
Send frame   9
Write packet   7 (size= 1977)
Send frame  10
Write packet  10 (size=12306)
Send frame  11
Write packet   9 (size= 2231)
Send frame  12
Write packet  12 (size= 3762)
Send frame  13
Write packet  11 (size= 2039)
[mpeg1video @ 02562100] warning, clipping 1 dct coefficients to -255..255
....
[mpeg1video @ 02562100] warning, clipping 1 dct coefficients to -255..255
[mpeg1video @ 02562100] waSend frame  14
Write packet  14 (size= 3278)
Send frame  15
Write packet  13 (size= 1939)
Send frame  16
Write packet  16 (size= 3150)
Send frame  17
Write packet  15 (size= 1929)
Send frame  18
Write packet  18 (size= 3422)
Send frame  19
Write packet  17 (size= 2116)
Send frame  20
Write packet  20 (size=12236)
Send frame  21
Write packet  19 (size= 2055)
Send frame  22
Write packet  22 (size= 4054)
Send frame  23
Write packet  21 (size= 2048)
Send frame  24
Write packet  24 (size= 3191)
Write packet  23 (size= 1955)
rning, clipping 1 dct coefficients to -255..255
[mpeg1video @ 02562100] warning, clipping 1 dct coefficients to -255..255
....
[mpeg1video @ 02562100] warning, clipping 1 dct coefficients to -255..255

在 debug 目录下生成一个 test666.mp4,使用 MediaInfo 查看相关信息如下:
在这里插入图片描述
可以看到只有一路视频流。使用 VLC 播放可以看到 1s 的视频如下:
在这里插入图片描述

五、解码案例实战

下面代码使用实现了一个视频解码,将指定的视频 test.flv 解码为原始视频数据 yuv 和原始音频数据 pcm

1、示例源码


/*** @file* Demuxing and decoding example.** Show how to use the libavformat and libavcodec API to demux and* decode audio and video data.* @example demuxing_decoding.c*/#include <libavutil/imgutils.h>
#include <libavutil/samplefmt.h>
#include <libavutil/timestamp.h>
#include <libavformat/avformat.h>static AVFormatContext *fmt_ctx = NULL;
static AVCodecContext *video_dec_ctx = NULL, *audio_dec_ctx;
static int width, height;
static enum AVPixelFormat pix_fmt;
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 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 output_video_frame(AVFrame *frame)
{if (frame->width != width || frame->height != height ||frame->format != pix_fmt) {/* To handle this change, one could call av_image_alloc again and* decode the following frames into another rawvideo file. */fprintf(stderr, "Error: Width, height and pixel format have to be ""constant in a rawvideo file, but the width, height or ""pixel format of the input video changed:\n""old: width = %d, height = %d, format = %s\n""new: width = %d, height = %d, format = %s\n",width, height, av_get_pix_fmt_name(pix_fmt),frame->width, frame->height,av_get_pix_fmt_name(frame->format));return -1;}printf("video_frame n:%d coded_n:%d\n",video_frame_count++, frame->coded_picture_number);/* 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,pix_fmt, width, height);/* write to rawvideo file */fwrite(video_dst_data[0], 1, video_dst_bufsize, video_dst_file);return 0;
}static int output_audio_frame(AVFrame *frame)
{size_t unpadded_linesize = frame->nb_samples * av_get_bytes_per_sample(frame->format);printf("audio_frame n:%d nb_samples:%d pts:%s\n",audio_frame_count++, frame->nb_samples,av_ts2timestr(frame->pts, &audio_dec_ctx->time_base));/* Write the raw audio data samples of the first plane. This works* fine for packed formats (e.g. AV_SAMPLE_FMT_S16). However,* most audio decoders output planar audio, which uses a separate* plane of audio samples for each channel (e.g. AV_SAMPLE_FMT_S16P).* In other words, this code will write only the first audio channel* in these cases.* You should use libswresample or libavfilter to convert the frame* to packed data. */fwrite(frame->extended_data[0], 1, unpadded_linesize, audio_dst_file);return 0;
}static int decode_packet(AVCodecContext *dec, const AVPacket *pkt)
{int ret = 0;// submit the packet to the decoderret = avcodec_send_packet(dec, pkt);if (ret < 0) {fprintf(stderr, "Error submitting a packet for decoding (%s)\n", av_err2str(ret));return ret;}// get all the available frames from the decoderwhile (ret >= 0) {ret = avcodec_receive_frame(dec, frame);if (ret < 0) {// those two return values are special and mean there is no output// frame available, but there were no errors during decodingif (ret == AVERROR_EOF || ret == AVERROR(EAGAIN))return 0;fprintf(stderr, "Error during decoding (%s)\n", av_err2str(ret));return ret;}// write the frame data to output fileif (dec->codec->type == AVMEDIA_TYPE_VIDEO)ret = output_video_frame(frame);elseret = output_audio_frame(frame);av_frame_unref(frame);if (ret < 0)return ret;}return 0;
}static int open_codec_context(int *stream_idx,AVCodecContext **dec_ctx, AVFormatContext *fmt_ctx, enum AVMediaType type)
{int ret, stream_index;AVStream *st;AVCodec *dec = NULL;AVDictionary *opts = 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_index = ret;st = fmt_ctx->streams[stream_index];/* find decoder for the stream */dec = avcodec_find_decoder(st->codecpar->codec_id);if (!dec) {fprintf(stderr, "Failed to find %s codec\n",av_get_media_type_string(type));return AVERROR(EINVAL);}/* Allocate a codec context for the decoder */*dec_ctx = avcodec_alloc_context3(dec);if (!*dec_ctx) {fprintf(stderr, "Failed to allocate the %s codec context\n",av_get_media_type_string(type));return AVERROR(ENOMEM);}/* Copy codec parameters from input stream to output codec context */if ((ret = avcodec_parameters_to_context(*dec_ctx, st->codecpar)) < 0) {fprintf(stderr, "Failed to copy %s codec parameters to decoder context\n",av_get_media_type_string(type));return ret;}/* Init the decoders */if ((ret = avcodec_open2(*dec_ctx, dec, &opts)) < 0) {fprintf(stderr, "Failed to open %s codec\n",av_get_media_type_string(type));return ret;}*stream_idx = stream_index;}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;
}int main(int argc, char **argv)
{int ret = 0;src_filename = "./debug/test.flv";video_dst_filename = "./debug/test.yuv";audio_dst_filename = "./debug/test.pcm";/* 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, &video_dec_ctx, fmt_ctx, AVMEDIA_TYPE_VIDEO) >= 0) {video_stream = fmt_ctx->streams[video_stream_idx];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 */width = video_dec_ctx->width;height = video_dec_ctx->height;pix_fmt = video_dec_ctx->pix_fmt;ret = av_image_alloc(video_dst_data, video_dst_linesize,width, height, 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, &audio_dec_ctx, fmt_ctx, AVMEDIA_TYPE_AUDIO) >= 0) {audio_stream = fmt_ctx->streams[audio_stream_idx];audio_dst_file = fopen(audio_dst_filename, "wb");if (!audio_dst_file) {fprintf(stderr, "Could not open destination file %s\n", audio_dst_filename);ret = 1;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) {// check if the packet belongs to a stream we are interested in, otherwise// skip itif (pkt.stream_index == video_stream_idx)ret = decode_packet(video_dec_ctx, &pkt);else if (pkt.stream_index == audio_stream_idx)ret = decode_packet(audio_dec_ctx, &pkt);av_packet_unref(&pkt);if (ret < 0)break;}/* flush the decoders */if (video_dec_ctx)decode_packet(video_dec_ctx, NULL);if (audio_dec_ctx)decode_packet(audio_dec_ctx, NULL);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(pix_fmt), width, height,video_dst_filename);}if (audio_stream) {enum AVSampleFormat sfmt = audio_dec_ctx->sample_fmt;int n_channels = audio_dec_ctx->channels;const char *fmt;if (av_sample_fmt_is_planar(sfmt)) {const char *packed = av_get_sample_fmt_name(sfmt);printf("Warning: the sample format the decoder produced is planar ""(%s). This example will output the first channel only.\n",packed ? packed : "?");sfmt = av_get_packed_sample_fmt(sfmt);n_channels = 1;}if ((ret = get_format_from_sample_fmt(&fmt, sfmt)) < 0)goto end;printf("Play the output audio file with the command:\n""ffplay -f %s -ac %d -ar %d %s\n",fmt, n_channels, audio_dec_ctx->sample_rate,audio_dst_filename);}end:avcodec_free_context(&video_dec_ctx);avcodec_free_context(&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_frame_free(&frame);av_free(video_dst_data[0]);return ret < 0;
}

2、运行结果

Demuxing video from file './debug/test.flv' into './debug/test.yuv'
Demuxing audio from file './debug/test.flv' into './debug/test.pcm'
video_frame n:0 coded_n:0
audio_frame n:0 nb_samples:1024 pts:0
audio_frame n:1 nb_samples:1024 pts:0.0004375
video_frame n:1 coded_n:1
audio_frame n:2 nb_samples:1024 pts:0.000895833
audio_frame n:3 nb_samples:1024 pts:0.00133333
video_frame n:2 coded_n:2
audio_frame n:4 nb_samples:1024 pts:0.00177083
audio_frame n:5 nb_samples:1024 pts:0.00222917
....
video_frame n:2931 coded_n:2931
audio_frame n:5496 nb_samples:1024 pts:2.44267
audio_frame n:5497 nb_samples:1024 pts:2.4431
audio_frame n:5498 nb_samples:1024 pts:2.44356
Demuxing succeeded.
Play the output video file with the command:
ffplay -f rawvideo -pix_fmt yuv420p -video_size 1280x720 ./debug/test.yuv
Warning: the sample format the decoder produced is planar (fltp). This example will output the first channel only.
Play the output audio file with the command:
ffplay -f f32le -ac 1 -ar 48000 ./debug/test.pcm
Input #0, flv, from './debug/test.flv':Metadata:encoder         : Lavf58.45.100Duration: 00:01:57.31, start: 0.000000, bitrate: 1442 kb/sStream #0:0: Video: h264 (Main), yuv420p(progressive), 1280x720 [SAR 1:1 DAR 16:9], 1048 kb/s, 25 fps, 25 tbr, 1k tbn, 50 tbcStream #0:1: Audio: aac (LC), 48000 Hz, 5.1, fltp, 383 kb/s

在 debug 目录下生成了原始视频数据 test.yuv 以及原始音频数据 test.pcm
在这里插入图片描述
在 debug 目录下打开终端使用 ffplay 播放 test.yuv:

ffplay -f rawvideo -video_size 1280x720 test.yuv

在这里插入图片描述
此时可以看到原视频中的视频(无音频)。

在 debug 目录下打开终端使用 ffplay 播放 test.pcm:

ffplay -f f32le -ac 1 -ar 48000 test.pcm

在这里插入图片描述
此时可以听到原视频中的音频(无视频)。


我的qq:2442391036,欢迎交流!


这篇关于AVFormatContext编解码层:理论与实战的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/476929

相关文章

从原理到实战深入理解Java 断言assert

《从原理到实战深入理解Java断言assert》本文深入解析Java断言机制,涵盖语法、工作原理、启用方式及与异常的区别,推荐用于开发阶段的条件检查与状态验证,并强调生产环境应使用参数验证工具类替代... 目录深入理解 Java 断言(assert):从原理到实战引言:为什么需要断言?一、断言基础1.1 语

Java MQTT实战应用

《JavaMQTT实战应用》本文详解MQTT协议,涵盖其发布/订阅机制、低功耗高效特性、三种服务质量等级(QoS0/1/2),以及客户端、代理、主题的核心概念,最后提供Linux部署教程、Sprin... 目录一、MQTT协议二、MQTT优点三、三种服务质量等级四、客户端、代理、主题1. 客户端(Clien

在Spring Boot中集成RabbitMQ的实战记录

《在SpringBoot中集成RabbitMQ的实战记录》本文介绍SpringBoot集成RabbitMQ的步骤,涵盖配置连接、消息发送与接收,并对比两种定义Exchange与队列的方式:手动声明(... 目录前言准备工作1. 安装 RabbitMQ2. 消息发送者(Producer)配置1. 创建 Spr

深度解析Spring Boot拦截器Interceptor与过滤器Filter的区别与实战指南

《深度解析SpringBoot拦截器Interceptor与过滤器Filter的区别与实战指南》本文深度解析SpringBoot中拦截器与过滤器的区别,涵盖执行顺序、依赖关系、异常处理等核心差异,并... 目录Spring Boot拦截器(Interceptor)与过滤器(Filter)深度解析:区别、实现

深度解析Spring AOP @Aspect 原理、实战与最佳实践教程

《深度解析SpringAOP@Aspect原理、实战与最佳实践教程》文章系统讲解了SpringAOP核心概念、实现方式及原理,涵盖横切关注点分离、代理机制(JDK/CGLIB)、切入点类型、性能... 目录1. @ASPect 核心概念1.1 AOP 编程范式1.2 @Aspect 关键特性2. 完整代码实

MySQL中的索引结构和分类实战案例详解

《MySQL中的索引结构和分类实战案例详解》本文详解MySQL索引结构与分类,涵盖B树、B+树、哈希及全文索引,分析其原理与优劣势,并结合实战案例探讨创建、管理及优化技巧,助力提升查询性能,感兴趣的朋... 目录一、索引概述1.1 索引的定义与作用1.2 索引的基本原理二、索引结构详解2.1 B树索引2.2

从入门到精通MySQL 数据库索引(实战案例)

《从入门到精通MySQL数据库索引(实战案例)》索引是数据库的目录,提升查询速度,主要类型包括BTree、Hash、全文、空间索引,需根据场景选择,建议用于高频查询、关联字段、排序等,避免重复率高或... 目录一、索引是什么?能干嘛?核心作用:二、索引的 4 种主要类型(附通俗例子)1. BTree 索引(

Java Web实现类似Excel表格锁定功能实战教程

《JavaWeb实现类似Excel表格锁定功能实战教程》本文将详细介绍通过创建特定div元素并利用CSS布局和JavaScript事件监听来实现类似Excel的锁定行和列效果的方法,感兴趣的朋友跟随... 目录1. 模拟Excel表格锁定功能2. 创建3个div元素实现表格锁定2.1 div元素布局设计2.

Redis 配置文件使用建议redis.conf 从入门到实战

《Redis配置文件使用建议redis.conf从入门到实战》Redis配置方式包括配置文件、命令行参数、运行时CONFIG命令,支持动态修改参数及持久化,常用项涉及端口、绑定、内存策略等,版本8... 目录一、Redis.conf 是什么?二、命令行方式传参(适用于测试)三、运行时动态修改配置(不重启服务

Python并行处理实战之如何使用ProcessPoolExecutor加速计算

《Python并行处理实战之如何使用ProcessPoolExecutor加速计算》Python提供了多种并行处理的方式,其中concurrent.futures模块的ProcessPoolExecu... 目录简介完整代码示例代码解释1. 导入必要的模块2. 定义处理函数3. 主函数4. 生成数字列表5.