使用FFmpeg把内存中的H264和AAC数据流合成MP4文件

FFmpeg 一般情况下支持打开一个本地文件,例如 “/usr/local/test.avi”、"/dev/video0",或者是一个流媒体协议的 URL,例如 “rtmp://222.31.64.208/vod/test.flv”、“http://192.168.199.160:7001/1/dd54fd96-8396-5faa-9cd4-0338ec1373e0.mp4”
其打开文件的函数是 avformat_open_input(),直接将文件路径或者流媒体 URL 的字符串传递给该函数的 url 参数就可以了
但它是否支持从内存中读取数据呢?例如从第三方获取到的数据就是封装好的 H264 数据,在程序中是存在内存中的某个 buffer 中
buffer 无法传递给 avformat_open_input() 函数,因为根本没有路径可言
如何持续读取发送来的内存块,如何边接收内存边封装为 mp4 格式文件?将收到的数据保存成文件,然后再用 FFmpeg 打开这个文件吗?这样的话,实时性就跟不上了
其实 FFmpeg 是可以从内存中读取数据的,关键要在 avformat_open_input() 之前初始化一个 AVIOContext,而且将原本的 AVFormatContext 的指针 pb 指向这个自行初始化的 AVIOContext
当指定了这个自行初始化的 AVIOContext 之后,avformat_open_input() 里面的 url 参数就不起作用了,即 url 可以传入 NULL
具体demo如下:

ffmpeg-3.4的配置可以参照前一篇文章的配置
https://blog.csdn.net/cfl927096306/article/details/107174939

Mp4Muxer.h 仅供参考,详见注释

#ifndef __MP4_MUXER_H__
#define __MP4_MUXER_H__

#include <thread>
#include <string>
#include <mutex>

#ifdef __cplusplus
extern "C" {
#endif
    #include "libavcodec/avcodec.h"
    #include "libavformat/avformat.h"
#ifdef __cplusplus
}
#endif

// 用于存储流数据的队列项结构
typedef struct _CE_QUEUE_ITEM_T_
{
    // 申请的缓冲区指针
    uint8_t* Bytes;
    // 申请的缓冲区大小
    int TotalSize;
    // 入队填充的数据字节数
    int EnqueueSize;
    // 出队读取的数据字节数
    int DequeueSize;
} CEQueueItem;

// 一个简单的队列结构, 用于暂存流数据
typedef struct _CE_QUEUE_T_
{
    // 队列项指针
    CEQueueItem *Items;
    // 队列的标识, 使用者可以自定义标识进行队列区分
    uint8_t Flag;
    // 队列项个数
    int Count;
    // 出队计数索引
    int DequeueIndex;
    // 入队索引
    int EnqueueIndex;
} CESimpleQueue;

//MP4混流器, 合成H264与AAC音视频
class Mp4Muxer
{
public:
    Mp4Muxer();
    ~Mp4Muxer();

    /*
    * Comments: 混流器初始化 实例化后首先调用该方法进行内部参数的初始化
    * Param aFileName: 要保存的文件名
    * @Return 是否成功
    */
    bool Start(std::string aFileName);

    /*
    * Comments: 追加一帧AAC数据到混流器
    * Param aBytes: 要追加的字节数据
    * Param aSize: 追加的字节数
    * @Return 成功与否
    */
    bool AppendAudio(uint8_t* aBytes, int aSize);

    /*
    * Comments: 追加一帧H264视频数据到混流器
    * Param aBytes: 要追加的字节数据
    * Param aSize: 追加的字节数
    * @Return 成功与否
    */
    bool AppendVideo(uint8_t* aBytes, int aSize);

    /*
    * Comments: 关闭并释放输出格式上下文
    * Param : None
    * @Return None
    */
    void Stop(void);

private:
    /*
    * Comments: 打开AAC输入上下文
    * Param : None
    * @Return 0成功, 其他失败
    */
    int OpenAudio(bool aNew = false);
    /*
    * Comments: 写入AAC数据帧到文件
    * Param : None
    * @Return void
    */
    void WriteAudioFrame(void);
    /*
    * Comments: 打开H264视频流
    * Param : None
    * @Return 0成功, 其他失败
    */
    int OpenVideo(void);
    /*
    * Comments: 写入H264视频帧到文件
    * Param : None
    * @Return void
    */
    void WriteVideoFrame(void);

private:
    // 要保存的MP4文件路径
    std::string mFileName;
    // 输入AAC音频上下文
    AVFormatContext* mInputAudioContext;
    // 输入H264视频上下文
    AVFormatContext* mInputVideoContext;
    // 输出媒体格式上下文
    AVFormatContext* mOutputFrmtContext;
    // AAC音频位流筛选上下文
    AVBitStreamFilterContext* mAudioFilter;
    // H264视频位流筛选上下文
    AVBitStreamFilterContext* mVideoFilter;
    // 流媒体数据包
    AVPacket mPacket;
    // AAC数据缓存区
    uint8_t* mAudioBuffer;
    // AAC数据读写队列
    CESimpleQueue* mAudioQueue;
    // H264数据缓冲区
    uint8_t* mVideoBuffer;
    // H264数据读写队列
    CESimpleQueue* mVideoQueue;
    // AAC的回调读取是否启动成功
    bool mIsStartAudio;
    // H264的回调读取是否启动成功
    bool mIsStartVideo;
    // 音频在mInputAudioContext->streams流中的索引
    int mAudioIndex;
    // 视频在mInputVideoContext->streams流中的索引
    int mVideoIndex;
    // 输出流中音频帧的索引
    int mAudioOutIndex;
    // 输出流视频帧的索引
    int mVideoOutIndex;
    // 输出帧索引计数
    int mFrameIndex;

    // 线程操作互斥锁
    std::mutex mLock;
};
#endif

Mp4Muxer.cpp 仅供参考,详见注释

#include "Mp4Muxer.h"

// av_read_frame读取AAC缓冲区大小 2K字节
#define AUDIO_BUFFER_SIZE       2048
// AAC队列项个数
#define AUDIO_QUEUE_SIZE        100
// AAC每个队列项缓冲区大小
#define AUDIO_ITEM_SIZE         2048
// AAC队列标识
#define AUDIO_QUEUE_FLAG        0x10

// av_read_frame读取H264缓冲区大小 40K字节
#define VIDEO_BUFFER_SIZE       40960
// H264队列项个数
#define VIDEO_QUEUE_SIZE        100
// H264每个队列项缓冲区大小
#define VIDEO_ITEM_SIZE         10240
// H264队列标识
#define VIDEO_QUEUE_FLAG        0x20

// 是否已经初始化过FFMPEG库
static bool mIsInitFFMPEG = false;

// aac静音数据, 用于填充MP4文件头
const uint8_t AAC_MUTE_DATA[361] = {
    0xFF,0xF1,0x6C,0x40,0x2D,0x3F,0xFC,0x00,0xE0,0x34,0x20,0xAD,0xF2,0x3F,0xB5,0xDD,
    0x73,0xAC,0xBD,0xCA,0xD7,0x7D,0x4A,0x13,0x2D,0x2E,0xA2,0x62,0x02,0x70,0x3C,0x1C,
    0xC5,0x63,0x55,0x69,0x94,0xB5,0x8D,0x70,0xD7,0x24,0x6A,0x9E,0x2E,0x86,0x24,0xEA,
    0x4F,0xD4,0xF8,0x10,0x53,0xA5,0x4A,0xB2,0x9A,0xF0,0xA1,0x4F,0x2F,0x66,0xF9,0xD3,
    0x8C,0xA6,0x97,0xD5,0x84,0xAC,0x09,0x25,0x98,0x0B,0x1D,0x77,0x04,0xB8,0x55,0x49,
    0x85,0x27,0x06,0x23,0x58,0xCB,0x22,0xC3,0x20,0x3A,0x12,0x09,0x48,0x24,0x86,0x76,
    0x95,0xE3,0x45,0x61,0x43,0x06,0x6B,0x4A,0x61,0x14,0x24,0xA9,0x16,0xE0,0x97,0x34,
    0xB6,0x58,0xA4,0x38,0x34,0x90,0x19,0x5D,0x00,0x19,0x4A,0xC2,0x80,0x4B,0xDC,0xB7,
    0x00,0x18,0x12,0x3D,0xD9,0x93,0xEE,0x74,0x13,0x95,0xAD,0x0B,0x59,0x51,0x0E,0x99,
    0xDF,0x49,0x98,0xDE,0xA9,0x48,0x4B,0xA5,0xFB,0xE8,0x79,0xC9,0xE2,0xD9,0x60,0xA5,
    0xBE,0x74,0xA6,0x6B,0x72,0x0E,0xE3,0x7B,0x28,0xB3,0x0E,0x52,0xCC,0xF6,0x3D,0x39,
    0xB7,0x7E,0xBB,0xF0,0xC8,0xCE,0x5C,0x72,0xB2,0x89,0x60,0x33,0x7B,0xC5,0xDA,0x49,
    0x1A,0xDA,0x33,0xBA,0x97,0x9E,0xA8,0x1B,0x6D,0x5A,0x77,0xB6,0xF1,0x69,0x5A,0xD1,
    0xBD,0x84,0xD5,0x4E,0x58,0xA8,0x5E,0x8A,0xA0,0xC2,0xC9,0x22,0xD9,0xA5,0x53,0x11,
    0x18,0xC8,0x3A,0x39,0xCF,0x3F,0x57,0xB6,0x45,0x19,0x1E,0x8A,0x71,0xA4,0x46,0x27,
    0x9E,0xE9,0xA4,0x86,0xDD,0x14,0xD9,0x4D,0xE3,0x71,0xE3,0x26,0xDA,0xAA,0x17,0xB4,
    0xAC,0xE1,0x09,0xC1,0x0D,0x75,0xBA,0x53,0x0A,0x37,0x8B,0xAC,0x37,0x39,0x41,0x27,
    0x6A,0xF0,0xE9,0xB4,0xC2,0xAC,0xB0,0x39,0x73,0x17,0x64,0x95,0xF4,0xDC,0x33,0xBB,
    0x84,0x94,0x3E,0xF8,0x65,0x71,0x60,0x7B,0xD4,0x5F,0x27,0x79,0x95,0x6A,0xBA,0x76,
    0xA6,0xA5,0x9A,0xEC,0xAE,0x55,0x3A,0x27,0x48,0x23,0xCF,0x5C,0x4D,0xBC,0x0B,0x35,
    0x5C,0xA7,0x17,0xCF,0x34,0x57,0xC9,0x58,0xC5,0x20,0x09,0xEE,0xA5,0xF2,0x9C,0x6C,
    0x39,0x1A,0x77,0x92,0x9B,0xFF,0xC6,0xAE,0xF8,0x36,0xBA,0xA8,0xAA,0x6B,0x1E,0x8C,
    0xC5,0x97,0x39,0x6A,0xB8,0xA2,0x55,0xA8,0xF8
};

/*
* Comments: 创建一个指定大小的队列
* Param aCount: 队列项的个数
* Param aItemSize: 队列每项的字节数
* @Return 创建的队列, 失败返回nullptr
*/
static CESimpleQueue* CreateQueue(int aCount, int aItemSize)
{
    CESimpleQueue* queue = (CESimpleQueue *)malloc(sizeof(CESimpleQueue));
    if (queue == nullptr)
    {
        return nullptr;
    }

    queue->Items = (CEQueueItem *)malloc(sizeof(CEQueueItem) * aCount);
    if (queue->Items == nullptr)
    {
        // 释放申请的内存
        free(queue);
        return nullptr;
    }
    for (int i = 0; i < aCount; i++)
    {
        queue->Items[i].Bytes = (uint8_t *)malloc(aItemSize);
        queue->Items[i].TotalSize = aItemSize;
        queue->Items[i].EnqueueSize = 0;
        queue->Items[i].DequeueSize = 0;
    }
    queue->Count = aCount;
    queue->DequeueIndex = 0;
    queue->EnqueueIndex = 0;

    return queue;
}

/*
* Comments: 销毁队列
* Param aQueue: 要销毁的队列
* @Return void
*/
static void DestroyQueue(CESimpleQueue* aQueue)
{
    if (aQueue == nullptr)
    {
        return;
    }

    // 释放队列所有申请的内存
    for (int i = 0; i < aQueue->Count; i++)
    {
        free(aQueue->Items[i].Bytes);
    }

    free(aQueue->Items);
    free(aQueue);
}

/*
* Comments: 队列数据入队操作
* Param aQueue: 队列实体, 函数内部不进行空检测
* Param aBytes: 要入队的数据
* Param aSize: 要入队的数据字节数
* @Return 入队成功与否
*/
static bool Enqueue(CESimpleQueue* aQueue, uint8_t* aBytes, int aSize)
{
    if (aBytes == NULL || aSize < 1)
    {
        return false;
    }

    int tail = aQueue->EnqueueIndex;
    int size = FFMIN(aSize, aQueue->Items[tail].TotalSize);
    while (size > 0)
    {
        memcpy(aQueue->Items[tail].Bytes, aBytes, size);
        aQueue->Items[tail].EnqueueSize = size;
        aQueue->Items[tail].DequeueSize = 0;
        aQueue->EnqueueIndex = (aQueue->EnqueueIndex + 1) % aQueue->Count;
        tail = aQueue->EnqueueIndex;
        // 数据过多, 继续入队
        aSize -= size;
        aBytes += size;
        size = FFMIN(aSize, aQueue->Items[tail].TotalSize);
    }

    return true;
}

/*
* Comments: 读取媒体内存流数据的FFMPEG回调方法 出队操作
* Param aHandle: 队列句柄
* Param aBytes: [输出] 要读取的数据
* Param aSize: 要读取的字节数
* @Return 读取到的实际字节个数
*/
static int ReadBytesCallback(void *aHandle, uint8_t *aBytes, int aSize)
{
    CESimpleQueue *pQueue = (CESimpleQueue *)aHandle;

    if (pQueue->DequeueIndex == pQueue->EnqueueIndex)
    {
        // printf("Queue[%02X] head index equal tail index: %d.\n", pQueue->Flag, pQueue->DequeueIndex);
        return 0;
    }

    int head = pQueue->DequeueIndex;
    // printf("Queue[%02X]%d deqSize:%d, enqSize:%d, aSize=%d.\n", pQueue->Flag, head,
    // pQueue->Items[head].DequeueSize, pQueue->Items[head].EnqueueSize, aSize);
    int size = FFMIN(aSize, pQueue->Items[head].EnqueueSize);

    // 复制数据出队
    memcpy(aBytes, pQueue->Items[head].Bytes + pQueue->Items[head].DequeueSize, size);

    pQueue->Items[head].DequeueSize += size;
    pQueue->Items[head].EnqueueSize -= size;

    if (pQueue->Items[head].EnqueueSize <= 0)
    {
        pQueue->Items[head].DequeueSize = 0;
        pQueue->DequeueIndex = (pQueue->DequeueIndex + 1) % pQueue->Count;
    }

    return size;
}

/*
* Comments: MP4混流器初始化
* Param :
* @Return void
*/
Mp4Muxer::Mp4Muxer()
{
    this->mInputAudioContext = nullptr;
    this->mInputVideoContext = nullptr;
    this->mOutputFrmtContext = nullptr;
    this->mAudioFilter = nullptr;
    this->mVideoFilter = nullptr;
    this->mAudioBuffer = nullptr;
    this->mVideoBuffer = nullptr;
    this->mIsStartAudio = false;
    this->mIsStartVideo = false;
    this->mAudioQueue = nullptr;
    this->mVideoQueue = nullptr;
    this->mAudioIndex = -1;
    this->mVideoIndex = -1;
    this->mAudioOutIndex = -1;
    this->mVideoOutIndex = -1;
    this->mFrameIndex = 0;
}

Mp4Muxer::~Mp4Muxer()
{
}

/*
* Comments: 混流器初始化 实例化后首先调用该方法进行内部参数的初始化
* Param aFileName: 要保存的文件名
* @Return 是否成功
*/
bool Mp4Muxer::Start(std::string aFileName)
{
    if (this->mOutputFrmtContext)
    {
        // 已经初始化过了.
        return true;
    }

    if (!mIsInitFFMPEG)
    {
        mIsInitFFMPEG = true;
        av_register_all();
    }

    if (aFileName.empty())
    {
        // 文件名为空
        return false;
    }

    printf("Start muxer: %s.\n", aFileName.c_str());

    // 分别创建AAC跟H264数据队列
    if (this->mAudioQueue == nullptr)
    {
        this->mAudioQueue = CreateQueue(AUDIO_QUEUE_SIZE, AUDIO_ITEM_SIZE);
        if (this->mAudioQueue == nullptr)
        {
            return false;
        }
        this->mAudioQueue->Flag = AUDIO_QUEUE_FLAG;
    }
    if (this->mVideoQueue == nullptr)
    {
        this->mVideoQueue = CreateQueue(VIDEO_QUEUE_SIZE, VIDEO_ITEM_SIZE);
        if (this->mVideoQueue == nullptr)
        {
            return false;
        }
        this->mVideoQueue->Flag = VIDEO_QUEUE_FLAG;
    }

    if (!mAudioFilter)
    {
        // 创建AAC滤波器
        mAudioFilter = av_bitstream_filter_init("aac_adtstoasc");
        if (!mAudioFilter)
        {
            // 创建失败
            printf("Init aac filter fail.\n");
            return false;
        }
    }

    if (!mVideoFilter)
    {
        // 创建H264滤波器
        mVideoFilter = av_bitstream_filter_init("h264_mp4toannexb");
        if (!mVideoFilter)
        {
            // 创建失败
            printf("Init h264 filter fail.\n");
            return false;
        }
    }

    this->mFrameIndex = 0;
    this->mFileName = aFileName;

    // 输出流上下文初始化
    int ret = avformat_alloc_output_context2(&this->mOutputFrmtContext, NULL, "mp4", this->mFileName.c_str());
    if (!this->mOutputFrmtContext)
    {
        printf("Alloc output format from file extension failed: %d!\n", ret);
        return false;
    }

    return true;
}

/*
* Comments: 关闭并释放输出格式上下文
* Param : None
* @Return None
*/
void Mp4Muxer::Stop(void)
{
    // lock
    std::lock_guard<std::mutex> lockGuard(this->mLock);

    printf("Stop muxer: %s.\n", this->mFileName.c_str());

    if (this->mOutputFrmtContext)
    {
        if (this->mIsStartVideo)
        {
            // 写入文件尾
            av_write_trailer(this->mOutputFrmtContext);
        }

        // 如果已创建文件则进行关闭
        if (!(this->mOutputFrmtContext->oformat->flags & AVFMT_NOFILE))
        {
            avio_closep(&this->mOutputFrmtContext->pb);
        }

        // 释放上下文
        avformat_free_context(this->mOutputFrmtContext);
        this->mOutputFrmtContext = nullptr;
    }

    // 释放滤波器
    if (mAudioFilter)
    {
        av_bitstream_filter_close(mAudioFilter);
        mAudioFilter = nullptr;
    }
    if (mVideoFilter)
    {
        av_bitstream_filter_close(mVideoFilter);
        mVideoFilter = nullptr;
    }

    // 释放输入流
    if (mInputAudioContext)
    {
        avio_context_free(&mInputAudioContext->pb);
        avformat_close_input(&mInputAudioContext);
        mInputAudioContext = nullptr;
        mAudioBuffer = nullptr;
    }
    else if (mAudioBuffer)
    {
        av_freep(&mAudioBuffer);
    }
    if (mInputVideoContext)
    {
        avio_context_free(&mInputVideoContext->pb);
        avformat_close_input(&mInputVideoContext);
        mInputVideoContext = nullptr;
        mVideoBuffer = nullptr;
    }
    else if (mVideoBuffer)
    {
        av_freep(&mVideoBuffer);
    }

    // 释放队列
    if (this->mAudioQueue)
    {
        DestroyQueue(mAudioQueue);
        mAudioQueue = nullptr;
    }
    if (this->mVideoQueue)
    {
        DestroyQueue(mVideoQueue);
        mVideoQueue = nullptr;
    }

}

/*
* Comments: 写入H264视频帧到文件
* Param : None
* @Return void
*/
void Mp4Muxer::WriteVideoFrame(void)
{
    int ret = -1;
    int count = 0;
    AVStream *in_stream, *out_stream;
    // 流媒体数据包
    AVPacket pkt;

    while (1)
    {
        // 获取一个数据包
        ret = av_read_frame(mInputVideoContext, &pkt);
        if (ret < 0)
        {
            // AVERROR_EOF表示流读取完
            if (ret != AVERROR_EOF)
            {
                printf("Read video frame ret = -%08X.\n", -ret);
            }
            return;
        }
        in_stream = mInputVideoContext->streams[mVideoIndex];
        out_stream = mOutputFrmtContext->streams[mVideoOutIndex];

        // 如果没有显示时间戳自己加上时间戳并且将显示时间戳赋值给解码时间戳
        if (pkt.pts == AV_NOPTS_VALUE)
        {
            //Write PTS
            AVRational time_base1 = in_stream->time_base;
            //Duration between 2 frames (us)
            int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(in_stream->r_frame_rate);
            //Parameters
            pkt.pts = (double)(mFrameIndex * calc_duration) / (double)(av_q2d(time_base1)*AV_TIME_BASE);
            pkt.dts = pkt.pts;
            pkt.duration = (double)calc_duration / (double)(av_q2d(time_base1)*AV_TIME_BASE);
            mFrameIndex++;
        }

        // H264视频处理
        av_bitstream_filter_filter(mVideoFilter, in_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);

        //Convert PTS/DTS
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.stream_index = mVideoOutIndex;
        pkt.pos = -1;

        //Write
        if (av_interleaved_write_frame(mOutputFrmtContext, &pkt) < 0)
        {
            av_free_packet(&pkt);
            printf("Error muxing packet.\n");
            break;
        }
        av_free_packet(&pkt);
    }
}

/*
* Comments: 追加一帧H264视频数据到混流器
* Param aBytes: 要追加的字节数据
* Param aSize: 追加的字节数
* @Return 成功与否
*/
bool Mp4Muxer::AppendVideo(uint8_t* aBytes, int aSize)
{
    std::lock_guard<std::mutex> lockGuard(this->mLock);

    if (aBytes == nullptr || aSize < 1)
    {
        return false;
    }

    if (!Enqueue(this->mVideoQueue, aBytes, aSize))
    {
        return false;
    }

    if (!this->mIsStartVideo)
    {
        if (OpenVideo() < 0)
        {
            return false;
        }
        this->mIsStartVideo = true;
    }
    else
    {
        WriteVideoFrame();
    }

    return true;
}

/*
* Comments: 打开H264视频流
* Param : None
* @Return 0成功, 其他失败
*/
int Mp4Muxer::OpenVideo(void)
{
    int result;
    AVIOContext* avioCtx = nullptr;
    AVInputFormat* inputFrmt = nullptr;

    if (mInputVideoContext == nullptr)
    {
        if (mVideoBuffer == nullptr)
        {
            // 申请视频数据缓冲区
            mVideoBuffer = (uint8_t*)av_malloc(VIDEO_BUFFER_SIZE);
            if (mVideoBuffer == nullptr)
            {
                return -1;
            }
        }

        // 设置FFMPEG的读取回调函数, 通过回调方式读取内存数据
        avioCtx = avio_alloc_context(mVideoBuffer, VIDEO_BUFFER_SIZE, 0, (void *)this->mVideoQueue, ReadBytesCallback, NULL, NULL);
        mInputVideoContext = avformat_alloc_context();
        if (mInputVideoContext == nullptr)
        {
            // 失败则释放上下文
            avio_context_free(&avioCtx);
            mVideoBuffer = nullptr;
            printf("Allock video format context fail.\n");
            return -1;
        }
        mInputVideoContext->pb = avioCtx; // 这一步很关键
    }

    if (this->mVideoQueue->EnqueueIndex - this->mVideoQueue->DequeueIndex < 10)
    {
        // 等待音频帧
        return -1;
    }

    // 探测流格式
    if ((result = av_probe_input_buffer(mInputVideoContext->pb, &inputFrmt, "", NULL, 0, 0)) < 0)
    {
        printf("Probe video context fail: -%08X.\n", -result);
        return result;
    }

    if (strcmp(inputFrmt->name, "h264") != 0)
    {
        printf("Error input video format name: %s.\n", inputFrmt->name);
        return -1;
    }

    // 打开输入流
    if ((result = avformat_open_input(&mInputVideoContext, "", inputFrmt, NULL)) != 0)
    {
        if (!mInputVideoContext)
        {
            mVideoBuffer = nullptr;
            if (avioCtx)
            {
                avio_context_free(&avioCtx);
            }
        }
        printf("Couldn't open input video stream -%08X.\n", -result);
        return result;
    }

    // 以下就和文件处理一致了

    // 查找流信息
    if ((result = avformat_find_stream_info(mInputVideoContext, NULL)) < 0)
    {
        printf("Failed to retrieve input video stream information -%08X.\n", -result);
        return result;
    }

    // 遍历输入媒体各个流格式
    for (unsigned int i = 0; i < mInputVideoContext->nb_streams; i++)
    {
        AVStream* input_stream = mInputVideoContext->streams[i];
        if (input_stream->codec->codec_type == AVMEDIA_TYPE_VIDEO)
        {
            if (input_stream->codec == nullptr)
            {
                printf("Input video codec is null.\n");
                return -2;
            }
            if ((input_stream->codec->coded_height < 1) || (input_stream->codec->coded_width < 1))
            {
                // 视频尺寸无效
                printf("Input video size invalid: %d, %d.\n", input_stream->codec->coded_width, input_stream->codec->coded_height);
                return -2;
            }
            mVideoIndex = i;
            AVStream* output_stream = avformat_new_stream(this->mOutputFrmtContext, input_stream->codec->codec);
            if (!output_stream)
            {
                printf("Create new video stream format failed!\n");
                return -2;
            }

            mVideoOutIndex = output_stream->index;
            if ((result = avcodec_copy_context(output_stream->codec, input_stream->codec)) < 0)
            {
                printf("Failed to copy video context from input to output stream codec context: -%08X!\n", -result);
                return result;
            }

            output_stream->codec->codec_tag = 0;
            if (this->mOutputFrmtContext->oformat->flags & AVFMT_GLOBALHEADER)
            {
                output_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
            }
            break;
        }
    }

    if (!this->mIsStartAudio)
    {
        // 没有等待音频帧则填充默认的音频帧
        Enqueue(this->mAudioQueue, (uint8_t *)AAC_MUTE_DATA, sizeof(AAC_MUTE_DATA));
        OpenAudio(true);
        this->mIsStartAudio = true;
    }

    // 根据需要打开输出文件
    if (!(this->mOutputFrmtContext->oformat->flags & AVFMT_NOFILE))
    {
        if ((result = avio_open(&this->mOutputFrmtContext->pb, this->mFileName.c_str(), AVIO_FLAG_WRITE)) < 0)
        {
            printf("Can't open file: %s: -%08X.\n", this->mFileName.c_str(), -result);
            return result;
        }
    }

    // 写入文件头
    if ((result = avformat_write_header(this->mOutputFrmtContext, NULL)) < 0)
    {
        printf("Error occurred when opening output file: -%08X!\n", -result);
        return result;
    }

    return 0;
}

/*
* Comments: 写入AAC数据帧到文件
* Param : None
* @Return void
*/
void Mp4Muxer::WriteAudioFrame(void)
{
    int result = -1;
    AVStream *in_stream, *out_stream;
    // 流媒体数据包
    AVPacket pkt;

    while (1)
    {
        AVStream *in_stream, *out_stream;
        // 获取一个数据包
        result = av_read_frame(mInputAudioContext, &pkt);
        if (result < 0)
        {
            // AVERROR_EOF表示流读取完
            // if (result != AVERROR_EOF)
            // {
            //     printf("Read audio frame result = -%08X.\n", -result);
            // }
            return;
        }
        in_stream = mInputAudioContext->streams[mAudioIndex];
        out_stream = mOutputFrmtContext->streams[mAudioOutIndex];

        // 如果没有显示时间戳自己加上时间戳并且将显示时间戳赋值给解码时间戳
        if (pkt.pts == AV_NOPTS_VALUE)
        {
            //Write PTS
            AVRational time_base1 = in_stream->time_base;
            //Duration between 2 frames (us)
            int64_t calc_duration = (double)AV_TIME_BASE / av_q2d(in_stream->r_frame_rate);
            //Parameters
            pkt.pts = (double)(mFrameIndex * calc_duration) / (double)(av_q2d(time_base1)*AV_TIME_BASE);
            pkt.dts = pkt.pts;
            pkt.duration = (double)calc_duration / (double)(av_q2d(time_base1)*AV_TIME_BASE);
            mFrameIndex++;
        }

        av_bitstream_filter_filter(mAudioFilter, out_stream->codec, NULL, &pkt.data, &pkt.size, pkt.data, pkt.size, 0);

        // Convert PTS/DTS
        pkt.pts = av_rescale_q_rnd(pkt.pts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        pkt.dts = av_rescale_q_rnd(pkt.dts, in_stream->time_base, out_stream->time_base, (AVRounding)(AV_ROUND_NEAR_INF | AV_ROUND_PASS_MINMAX));
        pkt.duration = av_rescale_q(pkt.duration, in_stream->time_base, out_stream->time_base);
        pkt.stream_index = mAudioOutIndex;
        pkt.pos = -1;
        // Write
        if (av_interleaved_write_frame(mOutputFrmtContext, &pkt) < 0)
        {
            av_free_packet(&pkt);
            printf("Error muxing packet\n");
            break;
        }
        av_free_packet(&pkt);
        //printf("Write %8d frames to output file\n",frame_index);
    }
}

/*
* Comments: 追加一帧AAC数据到混流器
* Param aBytes: 要追加的字节数据
* Param aSize: 追加的字节数
* @Return 成功与否
*/
bool Mp4Muxer::AppendAudio(uint8_t* aBytes, int aSize)
{
    std::lock_guard<std::mutex> lockGuard(this->mLock);

//     if (!this->mIsStartVideo)
//     {
//         return false;
//     }

    if (aBytes == nullptr || aSize < 1)
    {
        return false;
    }

    if (!Enqueue(this->mAudioQueue, aBytes, aSize))
    {
        return false;
    }

    if (!this->mIsStartAudio)
    {
        if (OpenAudio(false) < 0)
        {
            return false;
        }
        this->mIsStartAudio = true;
    }
    else if (this->mIsStartVideo)
    {
        WriteAudioFrame();
    }

    return true;
}

/*
* Comments: 打开AAC输入上下文
* Param : None
* @Return 0成功, 其他失败
*/
int Mp4Muxer::OpenAudio(bool aNew)
{
    int result;
    AVIOContext* pb = nullptr;
    AVInputFormat* inputFrmt = nullptr;

    if (aNew && mInputAudioContext)
    {
        // 重新申请
        avio_context_free(&mInputAudioContext->pb);
        avformat_close_input(&mInputAudioContext);
        mInputAudioContext = nullptr;
        mAudioBuffer = nullptr;
    }

    if (mInputAudioContext == nullptr)
    {
        if (mAudioBuffer == nullptr)
        {
            // 申请音频数据缓冲区
            mAudioBuffer = (uint8_t*)av_malloc(AUDIO_BUFFER_SIZE);
            if (mAudioBuffer == nullptr)
            {
                return -1;
            }
        }

        // 设置FFMPEG的读取回调函数, 通过回调方式读取内存数据
        pb = avio_alloc_context(mAudioBuffer, AUDIO_BUFFER_SIZE, 0, (void *)this->mAudioQueue, ReadBytesCallback, NULL, NULL);
        mInputAudioContext = avformat_alloc_context();
        if (mInputAudioContext == nullptr)
        {
            // 失败则释放上下文
            avio_context_free(&pb);
            mAudioBuffer = nullptr;
            printf("Alloc audio format context fail.\n");
            return -1;
        }
        mInputAudioContext->pb = pb;
    }

    if ((result = av_probe_input_buffer(mInputAudioContext->pb, &inputFrmt, "", NULL, 0, 0)) < 0)
    {
        printf("Probe audio context fail: -%08X.\n", -result);
        return result;
    }

    if (strcmp(inputFrmt->name, "aac") != 0)
    {
        printf("Error input audio format name: %s.\n", inputFrmt->name);
        return -1;
    }

    // 打开输入流
    if ((result = avformat_open_input(&mInputAudioContext, "", inputFrmt, NULL)) != 0)
    {
        if (!mInputAudioContext)
        {
            mAudioBuffer = nullptr;
            if (pb)
            {
                avio_context_free(&pb);
            }
        }
        printf("Couldn't open input audio stream -%08X.\n", -result);
        return result;
    }

    // 查找流信息
    if ((result = avformat_find_stream_info(mInputAudioContext, NULL)) < 0)
    {
        printf("Failed to retrieve input audio stream information -%08X.\n", -result);
        return result;
    }

    // 遍历输入媒体各个流格式
    for (unsigned int i = 0; i < mInputAudioContext->nb_streams; i++)
    {
        AVStream* input_stream = mInputAudioContext->streams[i];
        if (input_stream->codec->codec_type == AVMEDIA_TYPE_AUDIO)
        {
            mAudioIndex = i;
            AVStream* output_stream = avformat_new_stream(this->mOutputFrmtContext, input_stream->codec->codec);
            if (!output_stream)
            {
                printf("Create new audio stream format failed!\n");
                return -2;
            }

            mAudioOutIndex = output_stream->index;
            if ((result = avcodec_copy_context(output_stream->codec, input_stream->codec)) < 0)
            {
                printf("Failed to copy audio context from input to output stream codec context: -%08X!\n", -result);
                return result;
            }

            output_stream->codec->codec_tag = 0;
            if (this->mOutputFrmtContext->oformat->flags & AVFMT_GLOBALHEADER)
            {
                output_stream->codec->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
            }
            break;
        }
    }

    return  mAudioIndex >= 0 ? 0 : -1;
}

main.cpp 仅供参考,详见注释

#include "Mp4Muxer.h"

// refer to ffmpeg/libavformat/adtsenc.c
#define ADTS_HEADER_SIZE 7

const char* TestH264 = "./in.h264";
const char* TestAAC  = "./in.aac";
const char* TestMP4  = "./out.mp4";

/*
* Comments: 指定地址数据是否为H264帧起始标识, 有00 00 01 xx或00 00 00 01 xx两种起始标识
* Param aBytes: 要检测的内存地址
* @Return 是否为H264起始标识
*/
bool IsH264Flag(uint8_t *aBytes)
{
    if ((aBytes[0] == 0x00) && (aBytes[1] == 0x00)
        && (aBytes[2] == 0x00) && (aBytes[3] == 0x01))
    {
        uint8_t nal_type = aBytes[4] & 0x1F;
        // 1:P, 5:IDR, 6:SEI, 7:SPS, 8:PPS,
        return (nal_type == 0x01 || nal_type == 0x05 || nal_type == 0x06 || nal_type == 0x07 || nal_type == 0x08);
    }
    else if ((aBytes[0] == 0x00) && (aBytes[1] == 0x00)
        && (aBytes[2] == 0x01))
    {
        uint8_t nal_type = aBytes[3] & 0x1F;
        // 1:P, 5:IDR, 6:SEI, 7:SPS, 8:PPS,
        return (nal_type == 0x01 || nal_type == 0x05 || nal_type == 0x06 || nal_type == 0x07 || nal_type == 0x08);
    }

    return false;
}

bool IsAacFlag(uint8_t *aBytes)
{
    if ((aBytes[0] == 0xFF) && (aBytes[1] == 0xF1)
        && ((aBytes[5]&0x1F) == 0x1F) && (aBytes[6] == 0xFC))
    {
        return true;
    }

    return false;
}


/*
* Comments: 读取一帧H264数据
* Param aFile: 要读取的文件句柄
* Param aBytes: 读取到的帧数据
* @Return 大于0为读取到的帧数据个数, 否则失败
*/
int ReadH264Frame(FILE *aFile, uint8_t *aBytes)
{
    uint8_t *bytes = aBytes;
    int read_cnt = 0;
    bool is_head = false;
    int result = 0;

    while (true)
    {
        // 查找帧开头
        if ((result = fread(bytes, 1, 1, aFile)) < 1)
        {
            // 读完了
            return bytes - aBytes;
        }

        read_cnt++;
        bytes++;

        if (read_cnt >= 5)
        {
            if (IsH264Flag(bytes - 5))
            {
                if (!is_head)
                {
                    is_head = true;
                    memcpy(aBytes, bytes - 5, 5);
                    bytes = aBytes + 5;
                    read_cnt = 0;
                }
                else
                {
                    // 多读了5个字节, 需前移5个字节
                    bytes -= 5;
                    fseek(aFile, -5, SEEK_CUR);

                    return bytes - aBytes;
                }
            }
        }
    }
}

int ReadAacFrame(FILE *aFile, uint8_t *aBytes)
{
    uint8_t *bytes = aBytes;
    int read_cnt = 0;
    bool is_head = false;
    int result = 0;

    // 查找帧开头, ADTS头是7个字节
    if ((result = fread(bytes, ADTS_HEADER_SIZE, 1, aFile)) < 1)
    {
        // 读完了
        return 0;
    }

    // aac_frame_length = 7 + audio_len, 共占13bits
    if (IsAacFlag(bytes))
    {
        int aac_frame_length = ((bytes[3]&0x3)<<11) + (bytes[4]<<3) + ((bytes[5]&0xe0)>>5);
        if (aac_frame_length > 0)
        {
            if ((result = fread(bytes+ADTS_HEADER_SIZE, aac_frame_length-ADTS_HEADER_SIZE, 1, aFile)) < 1)
            {
                return ADTS_HEADER_SIZE;
            }
            return aac_frame_length;
        }
        else
        {
            return ADTS_HEADER_SIZE;
        }
    }

    return ADTS_HEADER_SIZE;
}

int main(int argc, char *argv[])
{
    AVFormatContext* fmt_h264 = NULL;
    AVFormatContext* fmt_aac = NULL;
    int result = 0;
    bool video_finished = false;
    bool audio_finished = false;
    FILE *pVideoFile = nullptr;
    FILE *pAudioFile = nullptr;
    uint8_t *read_video_pkt = nullptr;
    uint8_t *read_audio_pkt = nullptr;
    Mp4Muxer *muxer = nullptr;

    printf("Start muxer...\n");

    read_video_pkt = (uint8_t *)malloc(1024 * 1024);
    read_audio_pkt = (uint8_t *)malloc(1024 * 1024);

#if 1 // 此处只是为了打印一下H264和AAC的文件信息
    av_register_all();

    if ((result = avformat_open_input(&fmt_h264, TestH264, 0, 0)) < 0)
    {
        printf("Could not open input file: -%08X.\n", -result);
        goto end;
    }
    if ((result = avformat_find_stream_info(fmt_h264, 0)) < 0)
    {
        printf("Failed to get input stream information -%08X.\n", -result);
        goto end;
    }

    if ((result = avformat_open_input(&fmt_aac, TestAAC, 0, 0)) < 0)
    {
        printf("Could not open input file: -%08X.\n", -result);
        goto end;
    }
    if ((result = avformat_find_stream_info(fmt_aac, 0)) < 0)
    {
        printf("Failed to get input stream information -%08X.\n", -result);
        goto end;
    }

    // 输出文件信息
    av_dump_format(fmt_h264, 0, TestH264, 0);
    av_dump_format(fmt_aac, 0, TestAAC, 0);
    avformat_close_input(&fmt_h264);
    avformat_close_input(&fmt_aac);
#endif

    pVideoFile = fopen(TestH264, "rb");
    if (pVideoFile == nullptr)
    {
        printf("Open %s failed.\n", TestH264);
        goto end;
    }

    pAudioFile = fopen(TestAAC, "rb");
    if (pAudioFile == nullptr)
    {
        printf("Open %s failed.\n", TestAAC);
        goto end;
    }

    // 实例化混流器
    muxer = new Mp4Muxer();
    if (!muxer->Start(TestMP4))
    {
        delete muxer;
        printf("Muxer start fail.\n");
        goto end;
    }

    while (1)
    {
        // 读取一帧视频数据
        if (!video_finished)
        {
            // 此处不再是用 av_read_frame() 来读取一帧数据了
            if ((result = ReadH264Frame(pVideoFile, read_video_pkt)) <= 0)
            {
                printf("Read video frame fail: -%08X.\n", -result);
                video_finished = true;
                if (audio_finished)
                {
                    printf("Audio finished already, so stop!\n");
                    break;
                }
                continue;
            }
            // 需要确保传入的是一帧数据, 在ReadH264Frame()内部已确保了
            muxer->AppendVideo(read_video_pkt, result);
        }

        // 读取一帧音频数据
        if (!audio_finished)
        {
            // 此处不再是用 av_read_frame() 来读取一帧数据了
            if ((result = ReadAacFrame(pAudioFile, read_audio_pkt)) <= 0)
            {
                printf("Read audio frame fail: -%08X.\n", -result);
                audio_finished = true;
                if (video_finished)
                {
                    printf("Video finished already, so stop!\n");
                    break;
                }
                continue;
            }
            // 需要确保传入的是一帧数据, 在ReadAacFrame()内部已确保了
            muxer->AppendAudio(read_audio_pkt, result);
        }
    }

    muxer->Stop();
    delete muxer;

end:
    if(pVideoFile)
    {
        fclose(pVideoFile);
    }
    if(pAudioFile)
    {
        fclose(pAudioFile);
    }
    if(read_video_pkt)
    {
        free(read_video_pkt);
    }
    if(read_audio_pkt)
    {
        free(read_audio_pkt);
    }

    printf("Muxer finished.\n");
    return 0;
}

编译
arm-linux-g++ -std=c++11 main.cpp Mp4Muxer.cpp -o mp4muxer -I/ffmpeg/include -I./ -L/ffmpeg/lib -lavdevice -lavformat -lavcodec -lavutil -lswresample

运行
~# ./mp4muxer

Start muxer...
[aac @ 0xc1ac20] Estimating duration from bitrate, this may be inaccurate
Input #0, h264, from './in.h264':
  Duration: N/A, bitrate: N/A
    Stream #0:0: Video: h264, yuv420p(progressive), 1920x1080, 25 fps, 25 tbr, 1200k tbn, 50 tbc
Input #0, aac, from './in.aac':
  Duration: 00:00:59.68, bitrate: 68 kb/s
    Stream #0:0: Audio: aac, 44100 Hz, stereo, fltp, 68 kb/s
Start muxer: ./out.mp4.
Format aac detected only with low score of 1, misdetection possible!
[h264 @ 0xd50fc0] decoding for stream 0 failed
[mp4 @ 0xd467a0] Using AVStream.codec.time_base as a timebase hint to the muxer is deprecated. Set AVStream.time_base instead.
Ignoring attempt to set invalid timebase 1/0 for st:0
[mp4 @ 0xd467a0] Using AVStream.codec to pass codec parameters to muxers is deprecated, use AVStream.codecpar instead.
[mp4 @ 0xd467a0] Using AVStream.codec.time_base as a timebase hint to the muxer is deprecated. Set AVStream.time_base instead.
[mp4 @ 0xd467a0] Using AVStream.codec to pass codec parameters to muxers is deprecated, use AVStream.codecpar instead.
[NULL @ 0xd4f690] missing picture in access unit with size 20
[NULL @ 0xd4f690] missing picture in access unit with size 8
[NULL @ 0xd4f690] missing picture in access unit with size 20
[NULL @ 0xd4f690] missing picture in access unit with size 8
[NULL @ 0xd4f690] missing picture in access unit with size 20
[NULL @ 0xd4f690] missing picture in access unit with size 8
[NULL @ 0xd4f690] missing picture in access unit with size 20
[NULL @ 0xd4f690] missing picture in access unit with size 8
[NULL @ 0xd4f690] missing picture in access unit with size 20
[NULL @ 0xd4f690] missing picture in access unit with size 8
[NULL @ 0xd4f690] missing picture in access unit with size 20
[NULL @ 0xd4f690] missing picture in access unit with size 8
[NULL @ 0xd4f690] missing picture in access unit with size 20
[NULL @ 0xd4f690] missing picture in access unit with size 8
[NULL @ 0xd4f690] missing picture in access unit with size 20
[NULL @ 0xd4f690] missing picture in access unit with size 8
[NULL @ 0xd4f690] missing picture in access unit with size 20
[NULL @ 0xd4f690] missing picture in access unit with size 8
[NULL @ 0xd4f690] missing picture in access unit with size 20
[NULL @ 0xd4f690] missing picture in access unit with size 8
Read video frame fail: -00000000.
Read audio frame fail: -00000000.
Video finished already, so stop!
Stop muxer: ./out.mp4.
Muxer finished.

得到的out.mp4文件可以拿去PC VLC上播放

in.h264 和 in.aac 可以到这里去取
https://download.csdn.net/download/cfl927096306/12646402
https://download.csdn.net/download/cfl927096306/12646495

参考,非常感谢!
https://blog.csdn.net/xinxinsky/article/details/88531524

  • 2
    点赞
  • 11
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 6
    评论

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

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

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

cfl927096306

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值