基于 FFmpeg 的音视频处理基础原理与实验探究

news2025/4/22 19:43:28

目录

  • 1 基本知识
    • 1.1 解封装
    • 1.2 AAC和ADTS
      • 说明
    • 1.3 H264
      • 1.3.1 H264编码结构解析
      • 1.3.2 NALU
      • 1.3.2 分类
  • 2 实验1 探究音视频信息
    • 2.1 重要结构体介绍
    • 2.2 相关的API
  • 3 实验二 提取AAC数据
  • 4 实验三 提取h264

1 基本知识

1.1 解封装

在这里插入图片描述
封装的逆向操作:封装是把音频流、视频流、字幕流等不同成分按一定规则组合成视频文件(如 MP4、FLV ),复用器负责此过程。解封装则相反,是用解复用器(针对 MP4、FLV 等格式有对应解复用器)将视频文件再按规则拆分回音频流、视频流、字幕流等各个成分 。
流索引标识:解封装后,为区分各流,会给音频流、视频流等分配索引,如图中 audio_index = 1 表示音频流索引为 1 ,video_index = 0 表示视频流索引为 0 。程序后续可依这些索引分别处理对应流,比如解码视频流、播放音频流等 。

1.2 AAC和ADTS

AAC⾳频格式:Advanced Audio Coding(⾼级⾳频解码),是⼀种由MPEG-4
标准定义的有损⾳频压缩格式,由Fraunhofer发展,Dolby, Sony和AT&T是主要的贡献者。
分为两种封装格式

  • ADIF:Audio Data Interchange Format ⾳频数据交换格式。这种格式的特
    征是可以确定的找到这个⾳频数据的开始,不需进⾏在⾳频数据流中间开始
    的解码,即它的解码必须在明确定义的开始处进⾏。故这种格式常⽤在磁盘⽂件中。
  • ADTS的全称是Audio Data Transport Stream。是AAC⾳频的传输流格
    式。AAC⾳频格式在MPEG-2(ISO-13318-7 2003)中有定义。AAC后来
    ⼜被采⽤到MPEG-4标准中。这种格式的特征是它是⼀个有同步字的⽐特
    流,解码可以在这个流中任何位置开始。它的特征类似于mp3数据流格式

ADTS
在这里插入图片描述
有的时候当你编码AAC裸流的时候,会遇到写出来的AAC⽂件并不能在PC和⼿
机上播放,很⼤的可能就是AAC⽂件的每⼀帧⾥缺少了ADTS头信息⽂件的包装
拼接。
只需要加⼊头⽂件ADTS即可。⼀个AAC原始数据块⻓度是可变的,对原始帧加上ADTS头进⾏ADTS的封装,就形成了ADTS帧
ADTS帧
在这里插入图片描述
每⼀帧的ADTS的头⽂件都包含了⾳频的采样率,声道,帧⻓度等信息,这样解
码器才能解析读取。
⼀般情况下ADTS的头信息都是7个字节,分为2部分。

  • adts_fixed_header();
  • adts_variable_header();
    其⼀为固定头信息,紧接着是可变头信息。固定头信息中的数据每⼀帧都相
    同,⽽可变头信息则在帧与帧之间可变。
typedef struct {
    uint16_t syncword; // 同步字
    uint8_t id;        // MPEG标识符
    uint8_t layer;     // 层(固定为00)
    uint8_t protection_absent; // 是否有CRC校验
    uint8_t profile;   // AAC Profile
    uint8_t sampling_frequency_index; // 采样率索引
    uint8_t private_bit; // 私有位
    uint8_t channel_configuration; // 声道配置
    uint8_t original_copy; // 原始/拷贝标志
    uint8_t home;          // 主标志
    uint8_t copyright_identification_bit; // 版权标志位
    uint8_t copyright_identification_start; // 版权标志起始位
    uint16_t frame_length; // 帧长度
    uint8_t adts_buffer_fullness; // 缓存填充度
    uint8_t number_of_raw_data_blocks_in_frame; // 原始数据块数量
} ADTSHeader;

好的,以下是ADTS固定头参数的详细表格,包括字段名称、位长度、取值范围、含义和作用:

字段名称位长度取值范围含义作用
syncword120xFFF同步字标识一个ADTS帧的开始,用于帧同步。
ID10 或 1MPEG标识符区分MPEG版本:
0 = MPEG-4
1 = MPEG-2
Layer200固定为00,表示AAC编码。
protection_absent10 或 1误码校验标志表示是否包含CRC校验:
0 = 有CRC校验
1 = 无CRC校验
profile200-11AAC Profile(编码复杂度级别)表示编码复杂度:
00 = Main
01 = Low Complexity (LC)
10 = SSR
sampling_frequency_index40000-1111采样率索引通过索引查找采样率,例如:
0000 = 96 kHz
0100 = 44.1 kHz
private_bit10 或 1私有位用于私有用途,通常无具体含义。
channel_configuration30-7声道配置指定声道数和配置:
1 = 单声道
2 = 立体声
6 = 5.1声道
original_copy10 或 1原始/拷贝标志标识帧是否为原始数据:
0 = 拷贝
1 = 原始
home10 或 1主标志用于标识主音频流。
copyright_identification_bit10 或 1版权标志位标识是否包含版权信息。
copyright_identification_start10 或 1版权标志起始位标识版权信息的起始位置。

说明

  • syncword 是固定值 0xFFF,用于快速定位帧的起始位置
  • Layer 在AAC中始终为 00,因为AAC不使用MPEG音频的分层机制。
  • protection_absent 决定了帧头的长度(7字节或9字节)。
  • profilesampling_frequency_index 是关键字段,分别定义了编码复杂度和音频采样率。
  • channel_configuration 描述了音频的声道布局,例如立体声或5.1声道。
  • private_bitoriginal_copyhomecopyright_identification_bitcopyright_identification_start 主要用于扩展功能和版权保护。

1.3 H264

H.264从1999年开始,到2003年形成草案,最后在2007年定稿有待核实。在ITU的标准⾥称为H.264,在MPEG的标准⾥是MPEG-4的⼀个组成部分–MPEG-4 Part 10,⼜叫Advanced Video Codec,因此常常称为MPEG-4 AVC或直接叫AVC

H264编码原理
在⾳视频传输过程中,视频⽂件的传输是⼀个极⼤的问题;⼀段分辨率为19201080,每个像素点为RGB占⽤3个字节,帧率是25的视频,对于传输带宽的要求是:19201080325/1024/1024=148.315MB/s,换成bps则意味着视频每秒带宽为1186.523Mbps,这样的速率对于⽹络存储是不可接受的。因此视频压缩和编码技术应运⽽⽣。
对于视频⽂件来说,视频由单张图⽚帧所组成,⽐如每秒25帧,但是图⽚帧的像素块之间存在相似性,因此视频帧图像可以进⾏图像压缩;H264采⽤了16*16的分块⼤⼩对,视频帧图像进⾏相似⽐较和压缩编码
在这里插入图片描述
H264中的I帧、P帧和B帧
以下是提取的表格内容:

帧的分类中文意义
I帧帧内编码帧I帧通常是每个 GOP(MPEG 所使用的一种视频压缩技术)的第一个帧,经过适度地压缩,做为随机访问的参考点,可以当成图像。I帧可以看成是一个图像经过压缩后的产物。自身可以通过视频解压算法解压成一张单独的完整的图片
P帧前向预测编码帧通过充分将低于图像序列中前面已编码帧的时间冗余信息来压缩传输数据量的编码图像,也叫预测帧。需要参考其前面的一个I frame 或者P frame来生成一张完整的图片。
B帧双向预测帧既考虑与源图像序列前面已编码帧,也顾及源图像序列后面已编码帧之间的时间冗余信息来压缩传输数据量的编码图像, 也叫双向预测帧。则要参考其前一个I或者P帧及其后面的一个P帧来生成一张完整的图片

1.3.1 H264编码结构解析

IDR(Instantaneous Decoding Refresh,即时解码刷新)⼀个序列的第⼀个图像叫做 IDR 图像(⽴即刷新图像),IDR 图像都是 I 帧图像。I和IDR帧都使⽤帧内预测。I帧不⽤参考任何帧,但是之后的P帧和B帧是有可能参考这个I帧之前的帧的。IDR就不允许这样

原始图像 IDR1 B2 B3 P4 B5 B6 P7 B8 B9 I10
解码顺序:IDR1 P4 B2 B3 P7 B5 B6 I10 B8 B9 P13 B11 B12 P16 B14 B15

B8可以跨过I10去参考P7

IDR1 P4 B2 B3 P7 B5 B6 IDR8 P11 B9 B10 P14 B11 B12 这⾥的B9就只能参照IDR8和P1

其核⼼作⽤是,是为了解码的重同步,当解码器解码到 IDR 图像时,⽴即将参考帧队列清空,将已解码的数据全部输出或抛弃,重新查找参数集,开始⼀个新的序列。这样,如果前⼀个序列出现重⼤错误,在这⾥可以获得重新同步的机会。IDR图像之后的图像永远不会使⽤IDR之前的图像的数据来解码

**GOP (图像组)**主要⽤作形容⼀个IDR帧 到下⼀个IDR帧之间的间隔了多少个帧
在这里插入图片描述
H264除了实现了对视频的压缩处理之外,为了⽅便⽹络传输,提供了对应的视频编码和分⽚策略;类似于⽹络数据封装成IP帧,在H264中将其称为组(GOP, group of pictures)、⽚(slice)、宏块(Macroblock)这些⼀起组成了H264的码流分层结构;H264将其组织成为序列(GOP)、图⽚(pictrue)、⽚(Slice)、宏块(Macroblock)、⼦块(subblock)五个层次

1.3.2 NALU

在这里插入图片描述
SPS(序列参数集)

  • 作用:SPS包含了整个视频序列的全局参数,这些参数对整个视频序列都是有效的。
  • 内容:

视频的分辨率(宽度和高度)。
帧率信息。
编码使用的Profile和Level。
其他全局参数,例如色域、比特深度等。
重要性:解码器需要这些信息来初始化解码环境,并正确解析后续的视频数据。

PPS(图像参数集)

  • 作用:PPS包含了特定图像(或几幅图像)的参数,这些参数对图像层面的操作是必需的。
  • 内容:
    宏块的编码参数。
    量化参数。
    其他图像特定的设置。
    重要性:解码器需要这些信息来正确解码每个图像(帧)。

因此,在发送I帧之前,必须先发送SPS和PPS,以确保解码器能够正确处理后续的视频数据。

NALU 是 GOP 的组成部分:一个 GOP 中的图像帧(I 帧、P 帧、B 帧等 )会被进一步封装成一个个 NALU 进行存储和传输 。比如一个 I 帧可能会被分割成多个 NALU,每个 NALU 包含 I 帧的一部分编码数据 ;P 帧和 B 帧也会以类似方式处理 。
编码与传输层面的关联:GOP 侧重于视频编码结构设计,决定帧间预测关系和编码顺序等 ,影响视频压缩效率和随机访问性能 。NALU 则更关注在网络环境下如何有效传输编码数据,通过 NALU 头信息标识数据类型和重要性等,便于网络适配和差错控制 。实际应用中,视频编码先按 GOP 结构组织帧,再将每个帧的编码数据封装成 NALU 在网络中传输 。接收端先按 NALU 接收数据,再依据 GOP 结构和 NALU 信息进行解码恢复视频 。

NALU结构
H.264原始码流(裸流)是由⼀个接⼀个NALU组成,它的功能分为两层,VCL(视频编码层)和NAL(⽹络提取层):

  • VCL:包括核⼼压缩引擎和块,宏块和⽚的语法级别定义,设计⽬标是尽可能地独⽴于⽹络进⾏⾼效的编码;
  • NAL:负责将VCL产⽣的⽐特字符串适配到各种各样的⽹络和多元环境中,覆盖了所有⽚级以上的语法级别

NALU结构单元

  • Start Code(起始码)
    作用:用于标识一个NALU单元的开始。
    格式:通常是00 00 00 01或00 00 01。
    说明:在H.264码流中,起始码帮助解码器识别NALU的边界。00 00 00 01通常用于完整帧被编码为多个slice(片)的场合,而00 00 01用于其他场合。
  • NALU Header(NALU头)
    长度:1字节。
    组成:
    • forbidden_zero_bit:1位,必须为0,用于错误检测。
    • nal_ref_idc:2位,表示NALU的重要性,取值范围为00到11。值越大,表示当前NALU越重要,需要优先保护。
    • nal_unit_type:5位,表示NALU的类型,如I帧、P帧、B帧、SPS、PPS等。
  • NALU Payload(NALU负载)
    内容:实际的编码数据,也称为RBSP(Raw Byte Sequence Payload,原始字节序列负载)。
    说明:负载部分包含具体的视频数据,如编码后的图像数据、参数集等。

1.3.2 分类

H264有两种封装
⼀种是annexb模式,传统模式,有startcode,SPS和PPS是在ES中
⼀种是mp4模式,⼀般mp4 mkv都是mp4模式,没有startcode,SPS和PPS以及其它信息
被封装在container中,每⼀个frame前⾯4个字节是这个frame的⻓度
很多解码器只⽀持annexb这种模式,因此需要将mp4做转换:在ffmpeg中⽤
h264_mp4toannexb_filter可以做转换

const AVBitStreamFilter *bsfilter =av_bsf_get_by_name("h264_mp4toannexb");
AVBSFContext *bsf_ctx = NULL;
//2 初始化过滤器上下⽂
 av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
// 3 添加解码器属性 
avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
av_bsf_init(bsf_ctx);

2 实验1 探究音视频信息

2.1 重要结构体介绍

以下是将上述代码中涉及的重要结构体的常用成员整理成的表格:

AVFormatContext 结构体
作用:描述一个媒体文件或媒体流的构成和基本信息,如文件格式、流的数量和类型、时长、比特率等。它是 FFmpeg 处理媒体文件的核心结构体,许多操作都围绕它展开。

成员名称类型描述
nb_streamsunsigned int媒体文件中流的数量,例如视频、音频、字幕流等的总数
bit_rateint64_t媒体文件的平均比特率,单位为比特每秒(bps),反映数据传输的速率
durationint64_t媒体文件的总时长,单位为微秒(us)
streamsAVStream **指向 AVStream 结构体数组的指针,每个 AVStream 代表一个流,通过该指针可访问各流的信息
urlchar *媒体文件的路径或网络地址,调用 avformat_open_input 时读取到的文件标识

AVStream 结构体
作用:存储媒体文件中单个流(音频流、视频流、字幕流等 )的相关信息

成员名称类型描述
indexint该流在媒体文件中的索引,用于唯一标识每个流
time_baseAVRational流的时间基,是一个表示分数的结构体,包含分子和分母,用于将时间戳转换为实际时间
durationint64_t流的总时长,以 time_base 为单位,若值为 AV_NOPTS_VALUE 表示时长未知
codecparAVCodecParameters *指向 AVCodecParameters 结构体的指针,存储该流的编解码器参数信息
avg_frame_rateAVRational视频流的平均帧率,以每秒帧数(fps)为单位,反映视频的播放速度

AVCodecParameters 结构体
作用:存储编解码器的参数信息,如编解码器 ID、媒体类型、数据格式(像素格式或采样格式 )、视频的宽高(视频流 )、音频的采样率和声道数(音频流 )等。

成员名称类型描述
codec_idenum AVCodecID编解码器的 ID,用于标识具体的编解码器,如 AV_CODEC_ID_H264 代表 H.264 视频编解码器
codec_typeenum AVMediaType媒体类型,表明该流是视频、音频还是字幕等,如 AVMEDIA_TYPE_VIDEO 代表视频流
formatint数据格式,对于视频流是像素格式(如 AV_PIX_FMT_YUV420P),对于音频流是采样格式(如 AV_SAMPLE_FMT_FLTP
widthint视频帧的宽度,单位为像素,仅适用于视频流
heightint视频帧的高度,单位为像素,仅适用于视频流
sample_rateint音频采样率,单位为 Hz,表示每秒采集的样本数,仅适用于音频流
channelsint音频声道数,如单声道为 1,立体声为 2,仅适用于音频流

AVPacket 结构体
AVPacket
作用:存储从媒体文件中读取的一帧压缩数据(音频帧或视频帧 ),包含时间戳、数据大小、流索引等信息。

成员名称类型描述
ptsint64_t显示时间戳(Presentation Timestamp),用于标识该帧在播放时的时间点
dtsint64_t解码时间戳(Decoding Timestamp),用于标识该帧在解码时的时间点
sizeint数据包中数据的大小,单位为字节
stream_indexint该数据包所属流的索引,用于确定是音频流、视频流还是其他流的数据
durationint64_t该帧的时长,以 AVStreamtime_base 为单位,反映帧的持续时间

2.2 相关的API

  • avformat_network_init void avformat_network_init(void);
    初始化网络组件,让 FFmpeg 支持网络流的读取,像 RTSP、HTTP 这类网络协议的流
  • int avformat_open_input(AVFormatContext **ps, const char *url, ff_const59 AVInputFormat *fmt, AVDictionary **options);

ps:指向 AVFormatContext 指针的指针。函数会分配并初始化 AVFormatContext,并将其地址存储在 *ps 中。
url:输入媒体文件的路径或者网络地址。
fmt:指定输入格式,通常设为 NULL,让 FFmpeg 自动探测。
options:传递给解复用器的选项,是一个 AVDictionary 指针,通常设为 NULL。

  • int avformat_find_stream_info(AVFormatContext *ic, AVDictionary **options);

avformat_find_stream_info函数通过读取媒体文件的头部信息以及可能的一些额外数据,来填充AVFormatContext结构体中的各个字段,从而获取到流的信息,如流的数量、每个流的编码参数(包括视频的分辨率、帧率,音频的采样率、声道数等)、时长、比特率等。这些信息对于后续正确地解码和处理音视频数据非常重要
ic:指向 AVFormatContext 的指针,代表已打开的输入媒体文件。
options:传递给解复用器的选项,是一个 AVDictionary 指针,通常设为 NULL。

  • void av_dump_format(AVFormatContext *ic, int index, const char *url, int is_output);

功能:打印媒体文件的格式信息,涵盖流的数量、每个流的详细参数等。
参数:
ic:指向 AVFormatContext 的指针,代表已打开的输入媒体文件。
index:要打印信息的流的索引,通常设为 0 表示打印所有流的信息。
url:输入媒体文件的路径或者网络地址,用于显示信息。
is_output:指示是输入文件(0)还是输出文件(1)。

  • int av_find_best_stream(AVFormatContext *ic, enum AVMediaType type, int wanted_stream_nb, int related_stream, AVCodec **decoder_ret, int flags);

功能:在媒体文件的流中查找指定类型(如音频、视频)的最佳流。
参数:
ic:指向 AVFormatContext 的指针,代表已打开的输入媒体文件。
type:要查找的流的类型,如 AVMEDIA_TYPE_AUDIO 或 AVMEDIA_TYPE_VIDEO。
wanted_stream_nb:指定要查找的流的编号,若为 -1 则查找任意流。
related_stream:相关流的编号,通常设为 -1。
decoder_ret:若不为 NULL,则返回找到的流的解码器。
flags:查找标志,通常设为 0。

  • int av_read_frame(AVFormatContext *s, AVPacket *pkt);

AVFormatContext 结构体 ifmt_ctx 表示)中读取一帧压缩数据(音频帧或视频帧)的函数。每次调用该函数时,它会从文件或流中解析并提取下一帧数据,并将其存储在 AVPacket 结构体 pkt 中
参数:
s:指向 AVFormatContext 的指针,代表已打开的输入媒体文件。
pkt:指向 AVPacket 的指针,用于存储读取到的帧数据。
返回:成功返回 0,文件结束返回 AVERROR_EOF,失败返回负错误码。

  • av_packet_alloc AVPacket *av_packet_alloc(void);

功能:分配一个 AVPacket 结构体,用于存储读取到的媒体帧数据。
参数:无。
返回值:成功返回分配的 AVPacket 指针,失败返回 NULL。

  • void av_packet_unref(AVPacket *pkt);

功能:减少 AVPacket 的引用计数,当引用计数为 0 时释放相关资源。
参数:
pkt:指向 AVPacket 的指针,要释放引用的数据包。
返回值:无。

  • void av_packet_free(AVPacket **pkt);

功能:释放 AVPacket 结构体及其关联的资源。
参数:
pkt:指向 AVPacket 指针的指针,要释放的数据包。
返回值:无

3 实验二 提取AAC数据

int adts_header(char * const p_adts_header, const int data_length,
                const int profile, const int samplerate,
                const int channels)
{

    int sampling_frequency_index = 3; // 默认使用48000hz
    int adtsLen = data_length + 7;

    int frequencies_size = sizeof(sampling_frequencies) / sizeof(sampling_frequencies[0]);
    int i = 0;
    for(i = 0; i < frequencies_size; i++)
    {
        if(sampling_frequencies[i] == samplerate)
        {
            sampling_frequency_index = i;
            break;
        }
    }
    if(i >= frequencies_size)
    {
        printf("unsupport samplerate:%d\n", samplerate);
        return -1;
    }

    p_adts_header[0] = 0xff;         //syncword:0xfff                          高8bits
    p_adts_header[1] = 0xf0;         //syncword:0xfff                          低4bits
    p_adts_header[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bit
    p_adts_header[1] |= (0 << 1);    //Layer:0                                 2bits
    p_adts_header[1] |= 1;           //protection absent:1                     1bit

    p_adts_header[2] = (profile)<<6;            //profile:profile               2bits
    p_adts_header[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index  4bits
    p_adts_header[2] |= (0 << 1);             //private bit:0                   1bit
    p_adts_header[2] |= (channels & 0x04)>>2; //channel configuration:channels  高1bit

    p_adts_header[3] = (channels & 0x03)<<6; //channel configuration:channels 低2bits
    p_adts_header[3] |= (0 << 5);               //original:0                1bit
    p_adts_header[3] |= (0 << 4);               //home:0                    1bit
    p_adts_header[3] |= (0 << 3);               //copyright id bit:0        1bit
    p_adts_header[3] |= (0 << 2);               //copyright id start:0      1bit
    p_adts_header[3] |= ((adtsLen & 0x1800) >> 11);           //frame length:value   高2bits

    p_adts_header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);     //frame length:value    中间8bits
    p_adts_header[5] = (uint8_t)((adtsLen & 0x7) << 5);       //frame length:value    低3bits
    p_adts_header[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bits
    p_adts_header[6] = 0xfc;      //‭11111100‬       //buffer fullness:0x7ff 低6bits
    // number_of_raw_data_blocks_in_frame:
    //    表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。

    return 0;
}

补充:
|:或运算 有 1 这个位就是1
&:与运行 全为 1 才能是1
<<:左移运算符:移运算符规则:左移运算符 << 用于将一个二进制数向左移动指定的位数。其基本规则是,将操作数的二进制表示向左移动指定的位数,右边空出的位用 0 填充,而左边超出的位则被丢弃。
(0 << 3) 的计算:
数字 0 的二进制表示(假设为 8 位)是 0000 0000。
对其进行左移 3 位操作,即将所有位向左移动 3 位,右边空出的 3 位用 0 填充,得到的结果仍然是 0000 0000,也就是十进制的 0。
最终:p_adts_header[1] =0xf1;


size_t fwrite(const void *ptr, size_t size, size_t nmemb, FILE *stream);
size_t fread(void *ptr, size_t size, size_t nmemb, FILE *stream);

ptr:指向用于存储读取数据的内存块的指针。
size:每个数据项的大小(以字节为单位)。
nmemb:要读取的数据项的数量。
stream:指向 FILE 对象的指针,表示要读取的文件流。


FILE *fopen(const char *filename, const char *mode);

  • filename:这是一个字符串,代表要打开的文件的名称。如果文件在当前工作目录之外,需要提供完整的文件路径。
  • mode:同样是字符串,用于指定文件的打开模式。常见的打开模式如下:
    “r”:以只读模式打开文本文件。文件必须存在,否则打开失败。
    “w”:以写入模式打开文本文件。若文件存在,其内容会被清空;若文件不存在,则创建新文件。
    “a”:以追加模式打开文本文件。若文件存在,写入的数据会添加到文件末尾;若文件不存在,则创建新文件。
    “r+”:以读写模式打开文本文件。文件必须存在。
    “w+”:以读写模式打开文本文件。若文件存在,内容会被清空;若不存在,则创建新文件。
    “a+”:以读写模式打开文本文件。若文件存在,写入的数据会添加到文件末尾;若不存在,则创建新文件。
    若要以二进制模式操作文件,在上述模式后面加上字母 b,例如 “rb”、“wb” 等。

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>

#define ADTS_HEADER_LEN  7;

const int sampling_frequencies[] = {
    96000,  // 0x0
    88200,  // 0x1
    64000,  // 0x2
    48000,  // 0x3
    44100,  // 0x4
    32000,  // 0x5
    24000,  // 0x6
    22050,  // 0x7
    16000,  // 0x8
    12000,  // 0x9
    11025,  // 0xa
    8000   // 0xb
    // 0xc d e f是保留的
};

int adts_header(char * const p_adts_header, const int data_length,
                const int profile, const int samplerate,
                const int channels)
{

    int sampling_frequency_index = 3; // 默认使用48000hz
    int adtsLen = data_length + 7;

    int frequencies_size = sizeof(sampling_frequencies) / sizeof(sampling_frequencies[0]);//计算大小
    int i = 0;
    for(i = 0; i < frequencies_size; i++)
    {
        if(sampling_frequencies[i] == samplerate)
        {
            sampling_frequency_index = i;
            break;
        }
    }//音频采样
    if(i >= frequencies_size)
    {
        printf("unsupport samplerate:%d\n", samplerate);
        return -1;
    }

    p_adts_header[0] = 0xff;         //syncword:0xfff                          高8bits
    p_adts_header[1] = 0xf0;         //syncword:0xfff                          低4bits
    p_adts_header[1] |= (0 << 3);    //MPEG Version:0 for MPEG-4,1 for MPEG-2  1bit
    p_adts_header[1] |= (0 << 1);    //Layer:0                                 2bits
    p_adts_header[1] |= 1;           //protection absent:1                     1bit

    p_adts_header[2] = (profile)<<6;            //profile:profile               2bits
    p_adts_header[2] |= (sampling_frequency_index & 0x0f)<<2; //sampling frequency index:sampling_frequency_index  4bits
    p_adts_header[2] |= (0 << 1);             //private bit:0                   1bit
    p_adts_header[2] |= (channels & 0x04)>>2; //channel configuration:channels  高1bit

    p_adts_header[3] = (channels & 0x03)<<6; //channel configuration:channels 低2bits
    p_adts_header[3] |= (0 << 5);               //original:0                1bit
    p_adts_header[3] |= (0 << 4);               //home:0                    1bit
    p_adts_header[3] |= (0 << 3);               //copyright id bit:0        1bit
    p_adts_header[3] |= (0 << 2);               //copyright id start:0      1bit
    p_adts_header[3] |= ((adtsLen & 0x1800) >> 11);           //frame length:value   高2bits

    p_adts_header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);     //frame length:value    中间8bits
    p_adts_header[5] = (uint8_t)((adtsLen & 0x7) << 5);       //frame length:value    低3bits
    p_adts_header[5] |= 0x1f;                                 //buffer fullness:0x7ff 高5bits
    p_adts_header[6] = 0xfc;      //‭11111100‬       //buffer fullness:0x7ff 低6bits
    // number_of_raw_data_blocks_in_frame:
    //    表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧。

    return 0;
}

int main(int argc, char *argv[])
{
    int ret = -1;
    char errors[1024];

    char *in_filename = NULL;
    char *aac_filename = NULL;

    FILE *aac_fd = NULL;

    int audio_index = -1;
    int len = 0;


    AVFormatContext *ifmt_ctx = NULL;
    AVPacket pkt;

    // 设置打印级别
    av_log_set_level(AV_LOG_DEBUG);

    if(argc < 3)
    {
        av_log(NULL, AV_LOG_DEBUG, "the count of parameters should be more than three!\n");
        return -1;
    }

    in_filename = argv[1];      // 输入文件
    aac_filename = argv[2];     // 输出文件

    if(in_filename == NULL || aac_filename == NULL)
    {
        av_log(NULL, AV_LOG_DEBUG, "src or dts file is null, plz check them!\n");
        return -1;
    }

    aac_fd = fopen(aac_filename, "wb");
    if (!aac_fd)
    {
        av_log(NULL, AV_LOG_DEBUG, "Could not open destination file %s\n", aac_filename);
        return -1;
    }

    // 打开输入文件
    if((ret = avformat_open_input(&ifmt_ctx, in_filename, NULL, NULL)) < 0)
    {
        av_strerror(ret, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "Could not open source file: %s, %d(%s)\n",
               in_filename,
               ret,
               errors);
        return -1;
    }

    // 获取解码器信息
    if((ret = avformat_find_stream_info(ifmt_ctx, NULL)) < 0)
    {
        av_strerror(ret, errors, 1024);
        av_log(NULL, AV_LOG_DEBUG, "failed to find stream information: %s, %d(%s)\n",
               in_filename,
               ret,
               errors);
        return -1;
    }

    // dump媒体信息
    av_dump_format(ifmt_ctx, 0, in_filename, 0);

    // 初始化packet
    av_init_packet(&pkt);

    // 查找audio对应的steam index
    audio_index = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
    if(audio_index < 0)
    {
        av_log(NULL, AV_LOG_DEBUG, "Could not find %s stream in input file %s\n",
               av_get_media_type_string(AVMEDIA_TYPE_AUDIO),
               in_filename);
        return AVERROR(EINVAL);
    }

    // 打印AAC级别
    printf("audio profile:%d, FF_PROFILE_AAC_LOW:%d\n",
           ifmt_ctx->streams[audio_index]->codecpar->profile,
           FF_PROFILE_AAC_LOW);

    if(ifmt_ctx->streams[audio_index]->codecpar->codec_id != AV_CODEC_ID_AAC)
    {
        printf("the media file no contain AAC stream, it's codec_id is %d\n",
               ifmt_ctx->streams[audio_index]->codecpar->codec_id);
        goto failed;
    }
    // 读取媒体文件,并把aac数据帧写入到本地文件
    while(av_read_frame(ifmt_ctx, &pkt) >=0 )
    {
        if(pkt.stream_index == audio_index)
        {
            char adts_header_buf[7] = {0};
            adts_header(adts_header_buf, pkt.size,
                        ifmt_ctx->streams[audio_index]->codecpar->profile,
                        ifmt_ctx->streams[audio_index]->codecpar->sample_rate,
                        ifmt_ctx->streams[audio_index]->codecpar->channels);
            fwrite(adts_header_buf, 1, 7, aac_fd);  // 写adts header , ts流不适用,ts流分离出来的packet带了adts header
            len = fwrite( pkt.data, 1, pkt.size, aac_fd);   // 写adts data
            if(len != pkt.size)
            {
                av_log(NULL, AV_LOG_DEBUG, "warning, length of writed data isn't equal pkt.size(%d, %d)\n",
                       len,
                       pkt.size);
            }
        }
        av_packet_unref(&pkt);
    }

failed:
    // 关闭输入文件
    if(ifmt_ctx)
    {
        avformat_close_input(&ifmt_ctx);
    }
    if(aac_fd)
    {
        fclose(aac_fd);
    }

    return 0;
}

4 实验三 提取h264

#include <stdio.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
#include <libavformat/avformat.h>



static char err_buf[128] = {0};
static char* av_get_err(int errnum)
{
    av_strerror(errnum, err_buf, 128);
    return err_buf;
}

int main(int argc, char **argv)
{
    AVFormatContext *ifmt_ctx = NULL;
    int             videoindex = -1;
    AVPacket        *pkt = NULL;
    int             ret = -1;
    int             file_end = 0; // 文件是否读取结束

    if(argc < 3)
    {
        printf("usage inputfile outfile\n");
        return -1;
    }
    FILE *outfp=fopen(argv[2],"wb");
    printf("in:%s out:%s\n", argv[1], argv[2]);

    // 分配解复用器的内存,使用avformat_close_input释放
    ifmt_ctx = avformat_alloc_context();
    if (!ifmt_ctx)
    {
        printf("[error] Could not allocate context.\n");
        return -1;
    }

    // 根据url打开码流,并选择匹配的解复用器
    ret = avformat_open_input(&ifmt_ctx,argv[1], NULL, NULL);
    if(ret != 0)
    {
        printf("[error]avformat_open_input: %s\n", av_get_err(ret));
        return -1;
    }

    // 读取媒体文件的部分数据包以获取码流信息
    ret = avformat_find_stream_info(ifmt_ctx, NULL);
    if(ret < 0)
    {
        printf("[error]avformat_find_stream_info: %s\n", av_get_err(ret));
        avformat_close_input(&ifmt_ctx);
        return -1;
    }

    // 查找出哪个码流是video/audio/subtitles
    videoindex = -1;
    // 推荐的方式
    videoindex = av_find_best_stream(ifmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, NULL, 0);
    if(videoindex == -1)
    {
        printf("Didn't find a video stream.\n");
        avformat_close_input(&ifmt_ctx);
        return -1;
    }

    // 分配数据包
    pkt = av_packet_alloc();
    av_init_packet(pkt);

    // 1 获取相应的比特流过滤器
    //FLV/MP4/MKV等结构中,h264需要h264_mp4toannexb处理。添加SPS/PPS等信息。
    const AVBitStreamFilter *bsfilter = av_bsf_get_by_name("h264_mp4toannexb");
    AVBSFContext *bsf_ctx = NULL;
    // 2 初始化过滤器上下文
    av_bsf_alloc(bsfilter, &bsf_ctx); //AVBSFContext;
    // 3 添加解码器属性
    avcodec_parameters_copy(bsf_ctx->par_in, ifmt_ctx->streams[videoindex]->codecpar);
    av_bsf_init(bsf_ctx);

    file_end = 0;
    while (0 == file_end)
    {
        if((ret = av_read_frame(ifmt_ctx, pkt)) < 0)
        {
            // 没有更多包可读
            file_end = 1;
            printf("read file end: ret:%d\n", ret);
        }
        if(ret == 0 && pkt->stream_index == videoindex)
        {
#if 1//
            int input_size = pkt->size;
            int out_pkt_count = 0;
            if (av_bsf_send_packet(bsf_ctx, pkt) != 0) // bitstreamfilter内部去维护内存空间
            {
                av_packet_unref(pkt);   // 你不用了就把资源释放掉
                continue;       // 继续送
            }
            av_packet_unref(pkt);   // 释放资源
            while(av_bsf_receive_packet(bsf_ctx, pkt) == 0)
            {
                out_pkt_count++;
                // printf("fwrite size:%d\n", pkt->size);
                size_t size = fwrite(pkt->data, 1, pkt->size, outfp);
                if(size != pkt->size)
                {
                    printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);
                }
                av_packet_unref(pkt);
            }
            if(out_pkt_count >= 2)
            {
                printf("cur pkt(size:%d) only get 1 out pkt, it get %d pkts\n",
                       input_size, out_pkt_count);
            }

#else       // TS流可以直接写入 st

            size_t size = fwrite(pkt->data, 1, pkt->size, outfp);
            if(size != pkt->size)
            {
                printf("fwrite failed-> write:%u, pkt_size:%u\n", size, pkt->size);
            }
            av_packet_unref(pkt);
#endif
        }
        else
        {
            if(ret == 0)
                av_packet_unref(pkt);        // 释放内存
        }
    }
    if(outfp)
        fclose(outfp);
    if(bsf_ctx)
        av_bsf_free(&bsf_ctx);
    if(pkt)
        av_packet_free(&pkt);
    if(ifmt_ctx)
        avformat_close_input(&ifmt_ctx);
    printf("finish\n");

    return 0;
}

发送数据包到过滤器:运用 av_bsf_send_packet 函数把输入的数据包发送给比特流过滤器上下文 bsf_ctx。若发送失败,释放该数据包并继续处理下一个数据包。
接收处理后的数据包:通过 av_bsf_receive_packet 函数从过滤器中接收处理后的数据包,可能会接收到多个输出数据包。
写入文件:将每个处理后的数据包写入输出文件。
调试信息输出:若接收到的输出数据包数量超过预期(这里是大于等于 2 个),输出调试信息。


本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.coloradmin.cn/o/2340311.html

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈,一经查实,立即删除!

相关文章

我用deepseek做了一个提取压缩文件夹下pdf和word文件工具

由于最近需要把大量的压缩文件的pdf和word文件统一复制到一个文件夹中。 我们一般正常操作方式的是把一个压缩文件一个一个解压&#xff0c;然后在把一个的解压好的文件夹下文件复制到另外一个文件夹中。 这个也需太繁琐了&#xff0c;从以往统计的需要花费两个小时间&#x…

机器人进阶---视觉算法(五)仿射变换和投影变换有什么区别

仿射变换和投影变换有什么区别 1. 定义2. 几何特性3. 变换矩阵4. 应用场景5. Python代码示例仿射变换投影变换6. 总结仿射变换和投影变换都是图像处理中常用的几何变换方法,但它们在变换性质、应用场景和变换矩阵等方面存在一些关键区别。 1. 定义 仿射变换 (Affine Transform…

如何在 Amazon EC2 上部署 Java(Spring Boot 版)

让我们学习如何将 Java Spring Boot Web 服务器部署到 Amazon EC2。每月只需 3 美元。 使用 Azure&#xff0c;您可能不知道要花费多少钱。 Spring Boot 项目示例 在本教程中&#xff0c;我们将重点介绍如何将 Java Spring Boot 服务器部署到 Amazon EC2&#xff0c;因此我们不…

IDEA打不开、打开报错

目录 场景异常原因解决 场景 1、本机已经安装了IDEA 2、再次安装另外一个版本的IDEA后打不开、打开报错 异常 这里忘记截图了。。。 原因 情况1-打不开&#xff1a;在同一台电脑安装多个IDEA是需要对idea的配置文件进行调整的&#xff0c;否则打不开 情况2-打开报错&#…

【React】项目的搭建

create-react-app 搭建vite 搭建相关下载 在Vue中搭建项目的步骤&#xff1a;1.首先安装脚手架的环境&#xff0c;2.通过脚手架的指令创建项目 在React中有两种方式去搭建项目&#xff1a;1.和Vue一样&#xff0c;先安装脚手架然后通过脚手架指令搭建&#xff1b;2.npx create-…

CSS例子 > 图片瀑布流布局(vue2)

<template><div class"container"><!-- 临时容器用于计算高度 --><div v-if"!isLayoutReady" class"temp-container"><divv-for"(item, index) in list":key"temp- index":ref"(el) > …

1.2软考系统架构设计师:系统架构的定义与作用 - 练习题附答案及超详细解析

系统架构定义与作用综合知识单选题 题目覆盖核心概念、发展历程、设计原则、评估标准及易混淆点&#xff0c;附答案解析&#xff1a; 1. 系统架构的标准定义源自于以下哪个标准&#xff1f; A. ISO/IEC 9126 B. IEEE 1471-2000 C. TOGAF 9.2 D. ITIL v4 答案&#xff1a;B 简…

关于springmvc的404问题的一种猜测解决方案

本文是记录关于在学习动力结点老杜的springmvc时候遇到的404报错的一种解决方式&#xff1b; 由于本人之前学过老杜的springmvc&#xff0c;且运行成功&#xff0c;当时使用的是tomcat10.1.19版本。 idea使用2023.3.2版本。 而这次进行回顾的时候&#xff0c;使用tomcat10.0.1…

使用Postman调测“获取IAM用户Token”接口实际操作

概述 Postman是网页调试与辅助接口调用的工具&#xff0c;具有界面简洁清晰、操作方便快捷的特性&#xff0c;可以处理用户发送的HTTP请求&#xff0c;例如&#xff1a;GET&#xff0c;PUT、POST&#xff0c;DELETE等&#xff0c;支持用户修改HTTP请求中的参数并返回响应数据。…

如何测试雷达与相机是否时间同步?

在多传感器融合系统中&#xff0c;相机与雷达的协同感知已成为环境理解的关键。相机通过捕捉纹理信息识别物体类别&#xff0c;而雷达利用激光或毫米波实现全天候精确测距。两者的数据融合既能避免单一传感器缺陷&#xff08;如相机受光照影响、雷达缺乏语义信息&#xff09;&a…

爆肝整理!Stable Diffusion的完全使用手册(二)

继续介绍Stable Diffusion的文生图界面功能。 往期文章详见: 爆肝整理&#xff01;Stable Diffusion的完全使用手册&#xff08;一&#xff09; 下面接着对SD的文生图界面的进行详细的介绍。本期介绍文生图界面的截图2&#xff0c;主要包含生成模块下的采用方法、调度类型、迭…

OpenCV day5

函数内容接上文&#xff1a;OpenCV day4-CSDN博客 目录 9.cv2.adaptiveThreshold(): 10.cv2.split()&#xff1a; 11.cv2.merge()&#xff1a; 12.cv2.add()&#xff1a; 13.cv2.subtract()&#xff1a; 14.cv2.multiply()&#xff1a; 15.cv2.divide()&#xff1a; 1…

基于Spring Boot+微信小程序的智慧农蔬微团购平台-项目分享

基于Spring Boot微信小程序的智慧农蔬微团购平台-项目分享 项目介绍项目摘要目录系统功能图管理员E-R图用户E-R图项目预览登录页面商品管理统计分析用户地址添加 最后 项目介绍 使用者&#xff1a;管理员、用户 开发技术&#xff1a;MySQLSpringBoot微信小程序 项目摘要 随着…

WPF的发展历程

文章目录 WPF的发展历程引言起源与背景&#xff08;2001-2006&#xff09;从Avalon到WPF设计目标与创新理念 WPF核心技术特点与架构基础架构与渲染模型关键技术特点MVVM架构模式 WPF在现代Windows开发中的地位与前景当前市场定位与其他微软UI技术的关系未来发展前景 社区贡献与…

Franka机器人ROS 2来袭:解锁机器人多元应用新可能

前言&#xff1a; 在机器人技术蓬勃发展的当下&#xff0c;每一次创新都可能为行业带来新的变革。2025年3月12日&#xff0c;Franka Robotics发布的Franka ROS 2软件包首次版本0.1.0&#xff0c;将著名的franka_ros软件包引入当前的ROS 2 LTS Humble Hawksbill&#xff0c;这一…

树莓派5+Vosk+python实现语音识别

简介 Vosk是语音识别开源框架&#xff0c;支持二十种语言 - 中文&#xff0c;英语&#xff0c;印度英语&#xff0c;德语&#xff0c;法语&#xff0c;西班牙语&#xff0c;葡萄牙语&#xff0c;俄语&#xff0c;土耳其语&#xff0c;越南语&#xff0c;意大利语&#xff0c;荷…

数据结构——顺序表(C语言实现)

1.顺序表的概述 1.1 顺序表的概念及结构 在了解顺序表之前&#xff0c;我们要先知道线性表的概念&#xff0c;线性表&#xff0c;顾名思义&#xff0c;就是一个线性的且具有n个相同类型的数据元素的有限序列&#xff0c;常见的线性表有顺序表、链表、栈、队列、字符串等等。线…

STP原理与配置以及广播风暴实验STP实验

学习目标 环路引起的问题 掌握STP的工作原理 掌握STP的基本配置 STP的配置 环路引起的问题 一、广播风暴&#xff08;Broadcast Storm&#xff09; 问题原理&#xff1a; 交换机对广播帧&#xff08;如 ARP 请求、DHCP 发现报文&#xff09;的处理方式是洪泛&#xff0…

网络不可达network unreachable问题解决过程

问题&#xff1a;访问一个环境中的路由器172.16.1.1&#xff0c;发现ssh无法访问&#xff0c;ping发现回网络不可达 C:\Windows\System32>ping 172.16.1.1 正在 Ping 172.16.1.1 具有 32 字节的数据: 来自 172.16.81.1 的回复: 无法访问目标网。 来自 172.16.81.1 的回复:…

力扣经典拓扑排序

207. 课程表&#xff08;Course Schedule&#xff09; 你这个学期必须选修 numCourses 门课程&#xff0c;记为 0 到 numCourses - 1 。 在选修某些课程之前需要一些先修课程。先修课程按数组 prerequisites 给出&#xff0c;其中 prerequisites[i] [ai, bi] &#xff0c;表…