当前位置: 首页 > news >正文

基于ffmpeg的音视频编码

1 音频编码

本质上是由pcm文件转到一个协议文件 比如说aac协议

1.1 音频基本知识回归

  • 比特率
    比特率是指单位时间内传输或处理的比特(bit)数量,通常用 bps(bits per second,比特每秒)来表示。它是衡量数据传输速度和通信系统能力的重要指标。
  • 声道布局

声道布局是指在音频系统中,各个声道的扬声器在空间中的分布方式以及它们所承担的音频信息分配方式。以下是一些常见的声道布局简介:
单声道:只有一个声道,所有音频信息都通过一个扬声器播放,声音缺乏空间感和立体感,常用于一些简单的音频设备或对音频要求不高的场景。
立体声:有左声道和右声道两个声道,通过两个扬声器分别播放不同的音频信号,模拟人耳听到声音的立体感,能让听众感受到声音的左右位置差异,营造出一定的空间感,是音乐播放、电影音频等常见的声道布局。
5.1 声道:由左前、中置、右前、左环绕、右环绕五个扬声器以及一个重低音扬声器(.1 表示)组成。左前、中置和右前扬声器主要负责前方的声音,包括人物对话、主要乐器声等;左环绕和右环绕扬声器用于营造环境音效和后方声音,增强沉浸感;重低音扬声器专门负责低频音效,如爆炸声、雷声等,能提升声音的震撼力,常用于家庭影院系统。
7.1 声道:在 5.1 声道的基础上,增加了左后环绕和右后环绕两个声道,进一步增强了后方的声音细节和空间感,使听众能更清晰地感受到来自后方各个方向的声音,提供更逼真的环绕音效,常用于高端影院和一些专业音频制作环境。

  • 采样率
    采样率是指在单位时间内对模拟信号进行采样的次数,通常用赫兹(Hz)来表示
  • 采样格式
    即一个样本点多少位存放

packed格式:交错
1 AV_SAMPLE_FMT_U8, ///< unsigned 8 bits
2 AV_SAMPLE_FMT_S16, ///< signed 16 bits
3 AV_SAMPLE_FMT_S32, ///< signed 32 bits
4 AV_SAMPLE_FMT_FLT, ///< float
5 AV_SAMPLE_FMT_DBL, ///< double

planar格式 左右分别存放
1 AV_SAMPLE_FMT_U8P, ///< unsigned 8 bits, planar
2 AV_SAMPLE_FMT_S16P, ///< signed 16 bits, planar
3 AV_SAMPLE_FMT_S32P, ///< signed 32 bits, planar
4 AV_SAMPLE_FMT_FLTP, ///< float, planar
5 AV_SAMPLE_FMT_DBLP, ///< double, planar
6 AV_SAMPLE_FMT_S64, ///< signed 64 bits
7 AV_SAMPLE_FMT_S64P, ///< signed 64 bits, planar

  • 一帧大小
    计算出每一帧的数据 单个采样点的字节 * 通道数目 * 每帧采样点数量
  • aac协议
    aac协议由一个adts头和一个音频数据包组成
  • 编码器选择
    ffmpeg默认的aac编码器,默认编译出来的每帧数据都不带adts,但lib_fdk aac默认是带了adts header,而且此时codec_ctx->flags的值都为0.
    这样我们没法判断是否需要自己额外写入adts,因此我们在设置编码的时候可以直接将codec_ctx->flags = AV_CODEC_FLAG_GLOBAL_HEADER; 大家都不带adts

2.2 编码流程

在这里插入图片描述


const AVCodec *codec = NULL;//编码器
AVCodecContext *codec_ctx= NULL;//编码器设置codec = avcodec_find_encoder(codec_id)
codec_ctx = avcodec_alloc_context3(codec)codec_ctx->codec_id = codec_id;//idcodec_ctx->codec_type = AVMEDIA_TYPE_AUDIO;//typecodec_ctx->bit_rate = 128*1024;//码率codec_ctx->channel_layout = AV_CH_LAYOUT_STEREO;//立体声音codec_ctx->sample_rate    = 48000; //48000 采样率codec_ctx->channels       = av_get_channel_layout_nb_channels(codec_ctx->channel_layout);codec_ctx->profile = FF_PROFILE_AAC_LOW;    //低水平if(strcmp(codec->name, "aac") == 0) {codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;} else if(strcmp(codec->name, "libfdk_aac") == 0) {codec_ctx->sample_fmt = AV_SAMPLE_FMT_S16;} else {codec_ctx->sample_fmt = AV_SAMPLE_FMT_FLTP;}//采样格式/* 检测支持采样格式支持情况 */if (!check_sample_fmt(codec, codec_ctx->sample_fmt)) {fprintf(stderr, "Encoder does not support sample format %s",av_get_sample_fmt_name(codec_ctx->sample_fmt));exit(1);}if (!check_sample_rate(codec, codec_ctx->sample_rate)) {fprintf(stderr, "Encoder does not support sample rate %d", codec_ctx->sample_rate);exit(1);}if (!check_channel_layout(codec, codec_ctx->channel_layout)) {fprintf(stderr, "Encoder does not support channel layout %lu", codec_ctx->channel_layout);exit(1);}codec_ctx->flags = AV_CODEC_FLAG_GLOBAL_HEADER;
if (avcodec_open2(codec_ctx, codec, NULL) < 0) {fprintf(stderr, "Could not open codec\n");exit(1);}

这一部分 无疑完成编码器的初始化
先找编码器->再初始化上下文->再连接再一起

note:不同的编码器对采样格式有性能限制,aac编码器倾向于planar格式编码,libfdk_aac倾向于packed格式编码


AVFrame *frame = NULL;//为原始数据
AVPacket *pkt = NULL;//编码数据
pkt = av_packet_alloc();
frame = av_frame_alloc();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);ret = av_frame_get_buffer(frame, 0);int frame_bytes = av_get_bytes_per_sample(frame->format) * frame->channels * frame->nb_samples;uint8_t *pcm_buf = (uint8_t *)malloc(frame_bytes);
size_t read_bytes = fread(pcm_buf, 1, frame_bytes, infile);ret = av_frame_make_writable(frame);ret = av_samples_fill_arrays(frame->data, frame->linesize,pcm_buf, frame->channels,frame->nb_samples, frame->format, 0);

分配帧和数据包->初始化帧(给帧分配大小)->填充帧

ret = av_frame_get_buffer(frame, 0);0为让ffmpeg选择 是否对齐
av_samples_fill_arrays

uint8_t **audio_data
作用:这是一个指向指针数组的指针,用于存储每个声道的音频数据指针。AVFrame 中的 data 成员通常就是这样一个指针数组,用于存储不同声道的数据。例如,对于立体声(2 个声道),audio_data[0] 指向左声道数据,audio_data[1] 指向右声道数据。
示例:在 AVFrame 中使用时,通常传入 frame->data。
2. int *linesize
作用:这是一个指向整数数组的指针,用于存储每个声道数据的每行字节数(在音频数据中,“行” 可以理解为一个样本块)。这个值对于音频数据的正确处理非常重要,因为它表示了每个声道数据在内存中的布局信息。AVFrame 中的 linesize 成员就是用于存储这些信息的。
示例:在 AVFrame 中使用时,通常传入 frame->linesize。
3. const uint8_t *buf
作用:这是一个指向包含原始音频样本数据的缓冲区的指针。该缓冲区包含了要填充到 AVFrame 中的音频数据。
示例:在你的代码中,pcm_temp_buf 就是存储原始 PCM 音频数据的缓冲区,所以这里传入 pcm_temp_buf。
4. int nb_channels
作用:表示音频数据的声道数。例如,单声道音频的 nb_channels 为 1,立体声音频的 nb_channels 为 2。
示例:在 AVFrame 中使用时,通常传入 frame->channels。
?5. int nb_samples
作用:表示每个声道的样本数量。样本是音频数据的基本单位,nb_samples 决定了音频数据的时长和大小。
示例:在 AVFrame 中使用时,通常传入 frame->nb_samples。
6. enum AVSampleFormat sample_fmt
作用:表示音频样本的格式,它指定了每个样本的位深度、存储方式等信息。常见的样本格式有 AV_SAMPLE_FMT_S16(16 位有符号整数)、AV_SAMPLE_FMT_FLTP(浮点型平面格式)等。
示例:在 AVFrame 中使用时,通常传入 frame->format。
7. int align
作用:指定内存对齐方式。内存对齐可以提高内存访问效率,通常传入 0 让 FFmpeg 自动选择合适的对齐方式。


static int encode(AVCodecContext *ctx, AVFrame *frame, AVPacket *pkt, FILE *output)
{int ret;/* send the frame for encoding */ret = avcodec_send_frame(ctx, frame);if (ret < 0) {fprintf(stderr, "Error sending the frame to the encoder\n");return -1;}/* read all the available output packets (in general there may be any number of them */// 编码和解码都是一样的,都是send 1次,然后receive多次, 直到AVERROR(EAGAIN)或者AVERROR_EOFwhile (ret >= 0) {ret = avcodec_receive_packet(ctx, pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return 0;} else if (ret < 0) {fprintf(stderr, "Error encoding audio frame\n");return -1;}size_t len = 0;printf("ctx->flags:0x%x & AV_CODEC_FLAG_GLOBAL_HEADER:0x%x, name:%s\n",ctx->flags, ctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER, ctx->codec->name);if((ctx->flags & AV_CODEC_FLAG_GLOBAL_HEADER)) {// 需要额外的adts header写入uint8_t aac_header[7];get_adts_header(ctx, aac_header, pkt->size);len = fwrite(aac_header, 1, 7, output);if(len != 7) {fprintf(stderr, "fwrite aac_header failed\n");return -1;}}len = fwrite(pkt->data, 1, pkt->size, output);if(len != pkt->size) {fprintf(stderr, "fwrite aac data failed\n");return -1;}
}return -1;
}

发送帧->接受帧->写入文件


2 视频编码

本质上是将一个yuv 变成一个h264文件

2.1 h264编码细节推荐

1 什么是视频码率
视频码率是视频数据(包含视频⾊彩量、亮度量、像素量)每秒输出的位数。⼀般⽤的单位是kbps。
2 设置视频码率的必要性
在⽹络视频应⽤中,视频质量和⽹络带宽占⽤是相⽭盾的。通常情况下,视频流占⽤的带宽越⾼则视频
质量也越⾼,需要的⽹络带宽也越⼤,解决这⼀⽭盾的钥匙当然是视频编解码技术。评判⼀种视频编解码技术的优劣,是⽐较在相同的带宽条件下,哪个视频质量更好;在相同的视频质量条件下,哪个占⽤的⽹络带宽更少(⽂件体积⼩)。
是不是视频码率越⾼,质量越好呢?理论上是这样的。然⽽在我们⾁眼分辨的范围内,当码率⾼到⼀定程度时,就没有什么差别了。所以码率设置有它的最优值,H.264(也叫AVC或X264)的⽂件中,视频的建议码率如下
在这里插入图片描述
3 –preset的参数主要调节编码速度和质量的平衡,有ultrafast、superfast、veryfast、faster、fast、medium、slow、slower、veryslow、placebo这10个选项,从快到慢。

4 –tune的参数主要配合视频类型和视觉优化的参数,或特别的情况。如果视频的内容符合其中一个可用的调整值又或者有其中需要,则可以使用此选项,否则建议不使用(如tune grain是为高比特率的编码而设计的)。tune的值有:
film: 电影、真人类型;
animation: 动画;
grain: 需要保留大量的grain时用;
stillimage: 静态图像编码时使用;
psnr: 为提高psnr做了优化的参数;
ssim: 为提高ssim做了优化的参数;
fastdecode: 可以快速解码的参数;
zerolatency:零延迟,用在需要非常低的延迟的情况下,比如电视电话会议的编码。
5 在 H.264 编码中,profile(配置文件)用于指定编码视频流的特性和功能集合,不同的profile适用于不同的应用场景和设备。常见的 H.264 profile值及其特点如下:

  • Baseline Profile(基线配置文件)
    支持 I 帧(关键帧)和 P 帧(预测帧),不支持 B 帧(双向预测帧)。
    主要用于低延迟、低复杂度的应用,如视频会议、实时监控等。
    能够在相对较低的码率下提供较好的视频质量,适用于带宽有限的情况。
  • Main Profile(主配置文件)
    支持 I 帧、P 帧和 B 帧,提供了更高效的压缩性能。
    适用于大多数传统的视频应用,如视频存储、视频广播等。
    在相同的视频质量下,Main Profile 通常比 Baseline Profile 需要更高的码率,但能实现更好的压缩效果。
  • High Profile(高配置文件)
    进一步提高了压缩效率,支持更多的编码工具和特性,如更多的量化矩阵、自适应环路滤波等。
    主要用于高清和蓝光视频等对视频质量要求较高的应用。
    能够在高分辨率和高帧率的情况下提供出色的视频质量,但编码和解码的复杂度也相对较高。

baseline profile多应⽤于实时通信领域;
main profile多应⽤于流媒体领域;
high profile则多应⽤于⼴电和存储领域

6 bframes: I帧和P帧或者两个P帧之间可⽤的最⼤连续B帧数量,默认值为3。B帧可使⽤双向预测,从⽽显著提⾼压缩率,但由于需要缓存更多的帧数以及重排序的原因,会降低编码速度,增加编码延迟,因此在实时编码时也建议将该值设置为0。
在这里插入图片描述


const AVCodec *codec = NULL;
AVCodecContext *codec_ctx= NULL;codec = avcodec_find_encoder_by_name(codec_name);if (!codec) {fprintf(stderr, "Codec '%s' not found\n", codec_name);exit(1);}codec_ctx = avcodec_alloc_context3(codec);if (!codec_ctx) {fprintf(stderr, "Could not allocate video codec context\n");exit(1);}
codec_ctx->width = 1280;
codec_ctx->height = 720;
codec_ctx->time_base = (AVRational){1, 25};
codec_ctx->framerate = (AVRational){25, 1};
codec_ctx->gop_size = 25;   // I帧间隔
codec_ctx->max_b_frames = 2; // 如果不想包含B帧则设置为0
codec_ctx->pix_fmt = AV_PIX_FMT_YUV420P;* // ultrafast all encode time:2270ms// medium all encode time:5815ms// veryslow all encode time:19836ms*/ret = av_opt_set(codec_ctx->priv_data, "preset", "medium", 0);if(ret != 0) {printf("av_opt_set preset failed\n");}/*profile 参数:profile 表示 H.264 编码的配置文件,这里设置为 main。* 不同的 profile 支持不同的编码特性和功能,main 配置文件提供了基本的编码功能,适用于大多数视频应用场景。*/ret = av_opt_set(codec_ctx->priv_data, "profile", "main", 0); // 默认是highif(ret != 0) {printf("av_opt_set profile failed\n");}/*tune 参数:tune 表示针对特定应用场景进行优化,* 这里设置为 zerolatency,表示进行零延迟编码,适用于直播等对延迟要求较高的场景。*/ret = av_opt_set(codec_ctx->priv_data, "tune","zerolatency",0);codec_ctx->bit_rate = 3000000;ret = avcodec_open2(codec_ctx, codec, NULL);if (ret < 0) {fprintf(stderr, "Could not open codec: %s\n", av_err2str(ret));exit(1);}

同理 先找编码器->初始化上下文->再绑定


AVFrame *frame = NULL;
AVPacket *pkt = NULL;pkt = av_packet_alloc();if (!pkt) {fprintf(stderr, "Could not allocate video frame\n");exit(1);}frame = av_frame_alloc();if (!frame) {fprintf(stderr, "Could not allocate video frame\n");exit(1);}// 为frame分配bufferframe->format = codec_ctx->pix_fmt;frame->width  = codec_ctx->width;frame->height = codec_ctx->height;ret = av_frame_get_buffer(frame, 0);int frame_bytes = av_image_get_buffer_size(frame->format, frame->width,frame->height, 1);
int8_t *yuv_buf = (uint8_t *)malloc(frame_bytes);ret = av_frame_make_writable(frame);int need_size = av_image_fill_arrays(frame->data, frame->linesize, yuv_buf,frame->format,frame->width, frame->height, 1);

同理 设置帧参数


static int 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);/* 通过查阅代码,使用x264进行编码时,具体缓存帧是在x264源码进行,* 不会增加avframe对应buffer的reference*/ret = avcodec_send_frame(enc_ctx, frame);if (ret < 0){fprintf(stderr, "Error sending a frame for encoding\n");return -1;}while (ret >= 0){ret = avcodec_receive_packet(enc_ctx, pkt);if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {return 0;} else if (ret < 0) {fprintf(stderr, "Error encoding audio frame\n");return -1;}if(pkt->flags & AV_PKT_FLAG_KEY)//该数据包是否为关键帧printf("Write packet flags:%d pts:%3"PRId64" dts:%3"PRId64" (size:%5d)\n",pkt->flags, pkt->pts, pkt->dts, pkt->size);if(!pkt->flags)printf("Write packet flags:%d pts:%3"PRId64" dts:%3"PRId64" (size:%5d)\n",pkt->flags, pkt->pts, pkt->dts, pkt->size);fwrite(pkt->data, 1, pkt->size, outfile);}return 0

3 共同点

1.为什么要冲刷编码器

  • 清空缓存:编码器通常会有内部缓存来存储尚未输出的编码数据。在编码结束时或某些特定情况下,如切换编码参数、结束编码流程等,需要冲刷编码器,以确保这些缓存中的数据被完整地输出,避免数据丢失。
  • 保证数据完整性:冲刷操作可以使编码器将所有已处理但未发送的编码数据包发送出来,从而保证编码后的数据在文件或流中的完整性,使得后续的解码和播放等操作能够顺利进行。
  • 避免延迟:如果不进行冲刷,缓存中的数据可能会一直滞留在编码器中,导致输出延迟。冲刷编码器能够及时将数据输出,降低整体的编码延迟,特别是在实时性要求较高的场景,如直播、视频会议等中,这一点尤为重要。
  • 资源释放与重置:冲刷编码器有时也与资源的释放和重置相关。在完成一次编码任务后,通过冲刷可以清理编码器内部的临时资源,为下一次编码任务做好准备,确保编码器处于正确的初始状态,避免残留数据或状态对新的编码过程产生干扰。

2 为什么不要取消计数引用
avcodec_receive_packet 函数内部第一个调用的就是 av_packet_unref,它会对 AVPacket 进行引用计数的递减操作,并在引用计数为 0 时释放相关的资源。如果在外部再次手动调用 av_packet_unref,就会导致对同一个 AVPacket 的引用计数错误地多减一次,可能会造成资源过早释放,后续如果再使用该 AVPacket 相关的指针或数据,就会引发错误,比如出现野指针访问、程序崩溃等问题。

3 如何放入队列
由于编码器会释放数据,不能直接将原始的 pkt 插入到队列中。正确的做法是新分配一个 AVPacket,然后使用 av_packet_move_ref 函数将原始 pkt 对应的缓冲区转移到新分配的 AVPacket 中,再将新的 AVPacket 放入队列。这样做可以保证队列中的 AVPacket 拥有独立的缓冲区,不会因为编码器释放原始 pkt 的数据而导致队列中的数据被破坏。在放入队列时,还需要确保队列的操作是线程安全的,以避免多线程环境下的竞争条件和数据不一致问题。

4 流程不同
即aac要加头文件,以及编码器设置参数不一样 ,以及由两个api av_image_fill_arrays和av_samples_fill_arrays 不同

相关文章:

  • 复合模式(Composite Pattern)
  • 【Java开发日记】OpenFeign 的 9 个坑
  • Meta 推出 WebSSL 模型:探索 AI 无语言视觉学习,纯图训练媲美 OpenAI CLIP
  • 深入浅出限流算法(三):追求极致精确的滑动日志
  • leetcode283-移动零
  • 【神经网络与深度学习】两种加载 pickle 文件方式(joblib、pickle)的差异
  • uniapp自定义头部(兼容微信小程序(胶囊和状态栏),兼容h5)
  • 深度解析算法之分治(归并)
  • el-table 自定义列、自定义数据
  • 【网络编程】TCP/IP四层模型、MAC和IP
  • npm init、换源问题踩坑
  • 杰理-安卓通过map获取时间的时候,部分手机切换sbc和aac时候单耳无声音
  • redis+lua+固定窗口实现分布式限流
  • AcWing 885:求组合数 I ← 杨辉三角
  • seaborn数据统计可视化-介绍
  • 业绩回暖、股价承压,三只松鼠赴港上市能否重构价值锚点?
  • 道可云人工智能每日资讯|“人工智能科技体验展”在中国科学技术馆举行
  • GTC2025全球流量大会:领驭科技以AI云端之力,助力中国企业出海破浪前行
  • SECS-I vs HSMS-SS vs HSMS-GS 通信控制对比明细表
  • 可编程控制器应用
  • 北京动物园:大熊猫“萌兰”没有参加日本大阪世博会的计划
  • 广东雷州农商行董事长、原行长同日被查
  • 俄乌战火不熄,特朗普在梵蒂冈与泽连斯基会晤后口风突变
  • 药明康德一季度净利增长89%,在手订单增超四成至523亿元
  • 早睡1小时,变化有多惊人?第一个就没想到
  • 媒体:每一个“被偷走的人生”,都该得到公道和正义