FFmpeg封装ADTS为MP4
背景案例:
从海思芯片采集到的AAC数据为一帧帧ADTS数据,直接存为文件就是AAC了,可以直接播放,有时候需要与视频一起封装到MP4,
FFmpeg就可以做到这一点。
注:
修复上一次博客的如下代码可以实现ADTS封装为MP4,经过验证ffplay,vlc可以播放,但是H5 video标签以及某些播放器无法播放。
根本原因是需要在封装的时候,类似视频,需要在extradata中添加asc数据,asc数据可以通过adts转化而来.
下面直接贴参考代码如下:
/*
* @Description:
* @Author: xujin
* @Email: xujin_wuhan@163.com
* @Date: 2022-10-25 16:49:21
* @LastEditTime: 2022-11-03 23:42:28
* @LastEditors: xujin
* @Reference:
*/
/*
* @Description:
* @Author: xujin
* @Email: xujin_wuhan@163.com
* @Date: 2022-10-25 16:49:21
* @LastEditTime: 2022-11-11 13:04:58
* @LastEditors: xujin
* @Reference:
*/
#include <cstdio>
#include <string>
#include <fstream>
#include <memory>
#include <map>
extern "C" {
#include "libavformat/avformat.h"
}
#define SAMPLE_RATE 48000
#define SAMPLE_SIZE 1024
#define CHANNEL_SIZE 2
#define SAMPLE_BITS 32 // AV_SAMPLE_FMT_FLTP
#define AUDIO_FRAME_SIZE (SAMPLE_SIZE * CHANNEL_SIZE * SAMPLE_BITS)
#define DBG(fmt, args...) printf("|DBG|%s:%s:%d|" fmt, __FILE__, __FUNCTION__, __LINE__, ##args);printf("\n");
#define ERR(fmt, args...) printf("|ERR|%s:%s:%d|" fmt, __FILE__, __FUNCTION__, __LINE__, ##args);printf("\n");
#pragma pack(push, 1)
typedef struct AdtsHeader {
unsigned int syncword:12;//同步字0xfff,说明一个ADTS帧的开始
unsigned char ID:1;//ID比较奇怪,标准文档中是这么说的”MPEG identifier, set to ‘1’. See ISO/IEC 11172-3″,但我写0了,也可以
unsigned char layer:2;//一般设置为0
unsigned char protection_absent:1;//是否误码校验
unsigned char profile:2;//表示使用哪个级别的AAC,如01 Low Complexity(LC)--- AACLC
unsigned char sampling_frequency_index:4;//表示使用的采样率下标0x3 48k ,0x4 44.1k, 0x5 32k
unsigned char private_bit:1;//一般设置为0
unsigned char channel_configuration:3;// 表示声道数
unsigned char original_copy:1;//一般设置为0
unsigned char home:1;//一般设置为0
unsigned char copyright_identification_bit:1;//一般设置为0
unsigned char copyright_identification_start:1;//一般设置为0
unsigned int frame_length:13;// 一个ADTS帧的长度包括ADTS头和raw data block
unsigned int adts_buffer_fullness:11;// 0x7FF 说明是码率可变的码流
unsigned char number_of_raw_data_blocks_in_frame:2;//表示ADTS帧中有number_of_raw_data_blocks_in_frame + 1个AAC原始帧.
} AdtsHeader;
#pragma pack(pop)
int ParseAdtsHeaderFromData(AdtsHeader &header, const char *data, int size) {
if (!data || size < sizeof(AdtsHeader)) {
ERR("Failed to parse adts header");
return -1;
}
header.syncword = data[0] << 4 | ((data[1] & 0xf0) >> 4);
header.ID = (data[1] & 0x8) >> 3;
header.layer = (data[1] & 0x6) >> 1;
header.protection_absent = data[1] & 0x1;
header.profile = (data[2] & 0xc0) >> 6;
header.sampling_frequency_index = (data[2] & 0x3c) >> 2;
header.private_bit = (data[2] & 0x2)>> 1;
header.channel_configuration = ((data[2] & 0x01) << 2) | ((data[3] & 0xc0) >> 6);
header.original_copy = (data[3] & 0x20) >> 5;
header.home = (data[3] & 0x10) >> 4;
header.copyright_identification_bit = (data[3] & 0x08) >> 3;
header.copyright_identification_start = (data[3] & 0x04) >> 2;
header.frame_length = ((data[3] & 0x3) << 11) | (data[4] << 3) | ((data[5] &0xe0) >> 5); // 注意:对于移位操作,正确可以直接移,负数右移会左边补1导致bug,最好的做法是先&操作获取结果,然后再移位
header.adts_buffer_fullness = ((data[5] & 0x1f) << 6) | ((data[6] & 0xfc) >> 2);
header.number_of_raw_data_blocks_in_frame = data[7] & 0x3;
return 0;
}
// 先测试将ADTS封装为mp4 (将ADTS数据写成文件就是AAC数据了)
// ./a.out test.aac test.mp4
int main(int argc, char **argv) {
if (argc < 2) {
ERR("Usage, <%s> <aac_file> <output_mp4>", argv[0]);
return -1;
}
DBG("sizeof(AdtsHeader) = %lu", sizeof(AdtsHeader));
const std::string aac_filename(argv[1]);
const std::string mp4_filename(argv[2]);
std::ifstream aac_file(aac_filename);
if (!aac_file.is_open()) {
ERR("Failed to open %s", aac_filename.c_str());
return -1;
}
std::ofstream mp4_file(mp4_filename);
if (!mp4_file.is_open()) {
ERR("Failed open %s", mp4_filename.c_str());
return -1;
}
AVFormatContext *out_fmt_ctx = nullptr;
int ret = avformat_alloc_output_context2(&out_fmt_ctx, nullptr, "mp4", mp4_filename.c_str());
if (ret < 0 || !out_fmt_ctx) {
ERR("Failed to avformat_alloc_output_context2, ret:%d", ret);
return -1;
}
AVStream *audio_stream = avformat_new_stream(out_fmt_ctx, nullptr);
if (!audio_stream) {
ERR("Failed to avformat_new_stream");
return -1;
}
audio_stream->codecpar->sample_rate = SAMPLE_RATE;
audio_stream->codecpar->channels = CHANNEL_SIZE;
audio_stream->codecpar->channel_layout = av_get_default_channel_layout(audio_stream->codecpar->channels);
audio_stream->codecpar->format = AV_SAMPLE_FMT_FLTP;
// audio_stream->codecpar->bit_rate = 128 * 1024; // 对已知的数据数据做封装,设置与否都不起作用,该值在编码的时候设置
audio_stream->codecpar->bits_per_raw_sample = SAMPLE_BITS;
audio_stream->codecpar->codec_type = AVMEDIA_TYPE_AUDIO;
audio_stream->codecpar->codec_id = AV_CODEC_ID_AAC;
audio_stream->codecpar->frame_size = AUDIO_FRAME_SIZE;
audio_stream->codecpar->profile = 0x01; // 表示使用哪个级别的AAC,如01 Low Complexity(LC)--- AAC-LC
// 根据AdtsHeader获取ASC(MPEG-4 Audio Specific Configuration) [添加asc数据]
unsigned char asc[2] = {0};
std::map<int, int> sample_frequency_index_map;
sample_frequency_index_map.insert(std::pair<int,int>(96000, 0));
sample_frequency_index_map.insert(std::pair<int,int>(88200, 0x1));
sample_frequency_index_map.insert(std::pair<int,int>(64000, 0x2));
sample_frequency_index_map.insert(std::pair<int,int>(48000, 0x3));
sample_frequency_index_map.insert(std::pair<int,int>(44100, 0x4));
sample_frequency_index_map.insert(std::pair<int,int>(32000, 0x5));
sample_frequency_index_map.insert(std::pair<int,int>(24000, 0x6));
sample_frequency_index_map.insert(std::pair<int,int>(22050, 0x7));
sample_frequency_index_map.insert(std::pair<int,int>(16000, 0x8));
sample_frequency_index_map.insert(std::pair<int,int>(12000, 0x9));
sample_frequency_index_map.insert(std::pair<int,int>(11025, 0xa));
sample_frequency_index_map.insert(std::pair<int,int>(8000, 0xb));
sample_frequency_index_map.insert(std::pair<int,int>(7350, 0xc));
unsigned int sample_frequency_index = sample_frequency_index_map.find(48000)->second;
unsigned int object_type = 2; // AAC LC by default
unsigned int channel = 2;
asc[0] = (object_type << 3) | (sample_frequency_index >> 1);
asc[1] = ((sample_frequency_index & 0x1) << 7) | (channel << 3);
audio_stream->codecpar->extradata = (uint8_t *)av_malloc(sizeof(asc) + AV_INPUT_BUFFER_PADDING_SIZE);
memcpy(audio_stream->codecpar->extradata , asc, sizeof(asc));
audio_stream->codecpar->extradata_size = sizeof(asc);
av_dump_format(out_fmt_ctx, 0, nullptr, 1);
std::unique_ptr<char[]> audio_frame_buf(new char[AUDIO_FRAME_SIZE]{0});
if (!audio_frame_buf) {
ERR("Failed to get memory");
return -1;
}
int frameCount = 0;
unsigned int pts = 0;
while (!aac_file.eof()) {
memset(audio_frame_buf.get(), 0, AUDIO_FRAME_SIZE);
aac_file.read(audio_frame_buf.get(), sizeof(AdtsHeader));
if (aac_file.gcount() != sizeof(AdtsHeader)) {
continue;
}
AdtsHeader header;
ParseAdtsHeaderFromData(header, audio_frame_buf.get(), aac_file.gcount());
// DBG("frame_size:%d", header.frame_length);
aac_file.read(audio_frame_buf.get() + sizeof(AdtsHeader), header.frame_length - sizeof(AdtsHeader));
// 打开输出文件
if (!(out_fmt_ctx->oformat->flags & AVFMT_NOFILE) && !out_fmt_ctx->pb) {
DBG("avio_open");
avio_open(&out_fmt_ctx->pb, mp4_filename.c_str(), AVIO_FLAG_READ_WRITE);
if (avformat_write_header(out_fmt_ctx, nullptr) < 0) {
ERR("Failed to avformat_write_header");
return -1;
}
}
AVPacket pkt;
av_init_packet(&pkt);
pkt.stream_index = 0;
pkt.size = aac_file.gcount();
pkt.data = (uint8_t*)audio_frame_buf.get() + sizeof(AdtsHeader); // 将ADTS打包为mp4需要去掉ADTS头部(头部为7个字节)
pkt.pts = pts;
pkt.dts = pts;
pkt.duration = 1024;
pts += pkt.duration;
av_interleaved_write_frame(out_fmt_ctx, &pkt);
}
av_write_trailer(out_fmt_ctx);
avformat_close_input(&out_fmt_ctx);
aac_file.close();
return 0;
}