FFmpeg抽取音频数据涉及到以下几个函数:
av_init_packet()
初始化一个数据表结构体(音视频流是由一个个数据包组成)
av_find_best_stream()
在多媒体参数找到最好的一路流
av_read_frame()
读取流中的每一个包
av_packet_unref()
用于包数据不用了之后释放内存
注意:下面添加ADTS头的方法只适合HE-AAC 和 LC-AAC,对于其他类型的AAC用这个方法会导致抽取的音频无法播放。
关于AAC的ADTS头详解可以参考:AAC的ADTS头解析
/*
*提取视频中的音频数据
* */
#include <stdio.h>
#include <libavformat/avformat.h>
#include <libavutil/log.h>
#include <libavformat/avio.h>
/*
*ADTS头的实际长度是7个字节或9个字节,9个字节的是有2个Byte校验码的,这里以7字节为例
*设置音频的ADTS头信息;header为头信息的指针;dataLen为音频数据包的大小
*/
void adts_header(char *header, int dataLen){
// aac级别,0: AAC Main 1:AAC LC (Low Complexity) 2:AAC SSR (Scalable Sample Rate) 3:AAC LTP (Long Term Prediction)
int aac_type = 1;
// 采样率下标,下标7表示采样率为22050
int sampling_frequency_index = 7;
// 声道数
int channel_config = 2;
// ADTS帧长度,包括ADTS长度和AAC声音数据长度的和。
int adtsLen = dataLen + 7;
// syncword,标识一个帧的开始,固定为0xFFF,占12bit(byte0占8位,byte1占前4位)
header[0] = 0xff;
header[1] = 0xf0;
// ID,MPEG 标示符。0表示MPEG-4,1表示MPEG-2。占1bit(byte1第5位)
header[1] |= (0 << 3);
// layer,固定为0,占2bit(byte1第6、7位)
header[1] |= (0 << 1);
// protection_absent,标识是否进行误码校验。0表示有CRC校验,1表示没有CRC校验。占1bit(byte1第8位)
header[1] |= 1;
// profile,标识使用哪个级别的AAC。1: AAC Main 2:AAC LC 3:AAC SSR 4:AAC LTP。占2bit(byte2第1、2位)
header[2] = aac_type<<6;
// sampling_frequency_index,采样率的下标。占4bit(byte2第3、4、5、6位)
header[2] |= (sampling_frequency_index & 0x0f)<<2;
// private_bit,私有位,编码时设置为0,解码时忽略。占1bit(byte2第7位)
header[2] |= (0 << 1);
// channel_configuration,声道数。占3bit(byte2第8位和byte3第1、2位)
header[2] |= (channel_config & 0x04)>>2;
header[3] = (channel_config & 0x03)<<6;
// original_copy,编码时设置为0,解码时忽略。占1bit(byte3第3位)
header[3] |= (0 << 5);
// home,编码时设置为0,解码时忽略。占1bit(byte3第4位)
header[3] |= (0 << 4);
// copyrighted_id_bit,编码时设置为0,解码时忽略。占1bit(byte3第5位)
header[3] |= (0 << 3);
// copyrighted_id_start,编码时设置为0,解码时忽略。占1bit(byte3第6位)
header[3] |= (0 << 2);
// aac_frame_length,ADTS帧长度,包括ADTS长度和AAC声音数据长度的和。占13bit(byte3第7、8位,byte4全部,byte5第1-3位)
header[3] |= ((adtsLen & 0x1800) >> 11);
header[4] = (uint8_t)((adtsLen & 0x7f8) >> 3);
header[5] = (uint8_t)((adtsLen & 0x7) << 5);
// adts_buffer_fullness,固定为0x7FF。表示是码率可变的码流 。占11bit(byte5后5位,byte6前6位)
header[5] |= 0x1f;
header[6] = 0xfc;
// number_of_raw_data_blocks_in_frame,值为a的话表示ADST帧中有a+1个原始帧,(一个AAC原始帧包含一段时间内1024个采样及相关数据)。占2bit(byte6第7、8位)。
header[6] |= 0;
}
int main(int argc, char* argv[])
{
int flag;
char* src = NULL; // 输入文件路径
char* dst = NULL; // 提取后文件存储路径
AVFormatContext *fmt_ctx = NULL;
av_log_set_level(AV_LOG_INFO);
/*
*1.要提取的文件路径和提取后存储路径通过参数获取
* main函数第一个参数是函数名,加上2个路径参数,所以此时函数参数数量最少是3个
* */
if(argc < 3){
av_log(NULL,AV_LOG_ERROR,"参数数量最少为3个!\n");
return -1;
}
// 从参数中获取两个路径
src = argv[1];
dst = argv[2];
if(!src || !dst){
av_log(NULL,AV_LOG_ERROR,"传入的路径不能为空!\n");
return -1;
}
// 打开输入文件
flag = avformat_open_input(&fmt_ctx,src,NULL,NULL);
if(flag < 0){
av_log(NULL,AV_LOG_ERROR,"打开文件失败!\n");
// 退出程序之前记得关闭之前打开的文件释放内存
avformat_close_input(&fmt_ctx);
return -1;
}
// 打开要写入aac音频的文件,没有就会创建一个
FILE *file = fopen(dst,"wb");
if(file == NULL){
// 打开失败
av_log(NULL,AV_LOG_ERROR,"不能打开或创建存储文件!");
// 退出之前记得关闭之前打开的熟人文件来释放内存
avformat_close_input(&fmt_ctx);
return -1;
}
// 输出信息
av_dump_format(fmt_ctx,0,src,0);
/*
* 获取最好的一路流的资源
*第二个参数是流的类型
*第三个参数是流的索引号,不知道就写-1
*第四个参数是相关的对应的流的索引号,比如音频对应的视频流的索引号,可以不必关心填-1
*第五个参数是设置的编解码器,不设置就写NULL
*第六个参数是一些标准,暂时不关心填0
*返回值是所找到的流的索引值
* */
int audio_idx; // 音频索引值
audio_idx = av_find_best_stream(fmt_ctx,AVMEDIA_TYPE_AUDIO,-1,-1,NULL,0);
if(audio_idx < 0){
av_log(NULL,AV_LOG_ERROR,"获取流失败!");
// 退出前关闭相关文件释放内存
avformat_close_input(&fmt_ctx);
fclose(file);
return -1;
}
// 读取流中的包数据
AVPacket pkt;
av_init_packet(&pkt); // 初始化pkt
// 循环读取流中所有的包(这里注意传进去的是pkt的地址)
while(av_read_frame(fmt_ctx,&pkt) >= 0){
// 判断读取的包所属的流是否是我们找到的流
if(pkt.stream_index == audio_idx){
// 如果读取的包是我们找的流就写入文件(在写入之前先在包数据前加上ADTS头(7byte),否则存储的AAC文件无法播放)
char adts_header_buf[7];
adts_header(adts_header_buf,pkt.size);
fwrite(adts_header_buf,1,7,file);
if(fwrite(pkt.data,1,pkt.size,file) != pkt.size){
av_log(NULL,AV_LOG_WARNING,"警告,写入的大小与返回的大小不一致!");
}
}
// 因为每次循环都要为pkt分配内存,所以一轮循环结束时要释放内存
av_packet_unref(&pkt);
}
avformat_close_input(&fmt_ctx);
if(file){
fclose(file);
}
return 0;
}
注意在编译完成后执行时要传入输入文件和输出文件的路径作为参数:
./extraAudio ./test.mp4 ./test.aac
网友评论