FFmpeg封装ADTS为MP4

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;
}
  • 0
    点赞
  • 0
    收藏
    觉得还不错? 一键收藏
  • 1
    评论

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值