使用ffmpeg将内存中的裸流打包成可播放的MP4文件,并输出到内存中

发布时间 2023-05-24 19:55:22作者: 阿风小子

 前两天项目上有个需求,要求大概是这样的,输入端是一帧一帧的h264裸流(本示例只支持h264裸流,h265可基于本示例自己开发,在此我就不过多阐述了)和一个时间,要求输出根据这个时间来产生一个前后各延伸一段时间的视频(伴随录像),且伴随录像是可直接播放的MP4文件。但是产生的视频文件不是直接存储在本地的某个文件夹下,而是通过网络,向外部的某个ftp服务器发送,在存储到ftp服务器的路径下。

        基于上述的需求,我将过程大致分成了两步,第一步是取内存中的h264裸流,经过ffmpeg打包成可播放的MP4文件,但是输出到内存中,第二步是将内存中的MP4文件通过ftp发送到服务器。本文所要阐述的是第一步。第二步后续有时间我会继续更新(如果你急需可留言联系我)。

        网上关于ffmpeg的开发,多多少少都有参考大神雷霄骅(在此致敬,感谢雷大大做的贡献)的资料,我同样也不例外,他的资料中,读取本地h264裸流文件后打包并输出到MP4文件、和输入rtsp裸流打包并输出到MP4文件的相关资料很全,但是关于从内存中获取h264裸流并输出到内存中的资料确不多,我开发的过程中,只找到一篇雷大大的伪代码,只提供了思路和少数的几行代码,基于这篇伪代码,我在开发过程中遇到了不少问题,所以在此分享,希望能在雷大大的那面“墙”上添上一块转,对广大码友有所帮助。

本示例基于ffmpeg2.8.8版本 (库链接顺序 -lavformat -lavcodec -lavutil -lswscale -lswresample -lm -lpthread -ldl)

HI_ES2MP4.c  

#include <stdio.h>
#include "libavformat/avformat.h"
#include "libavcodec/avcodec.h"
#include "pack_h264.h"


extern ITS_ALONG_EVENT_INFO *          g_along_event;

static AVFormatContext *               g_OutFmt_Ctx;
static int                             vi = -1;
//static HI_BOOL b_First_IDR_Find = HI_FALSE;
static int                             ptsInc = 0;

/* Add an output stream */
static int HI_PDT_Add_Stream(AVFormatContext *poutFmtCtx, int h264_rate, int h264_w, int h264_h)
{
    AVOutputFormat *pOutFmt = NULL;
    AVCodecContext *PAVCodecCtx = NULL;
    AVStream *pAVStream = NULL;
    AVCodec *pcodec = NULL;

    pOutFmt = poutFmtCtx->oformat;

    /* find the encoder */
    pcodec = avcodec_find_encoder(pOutFmt->video_codec);
    if (NULL == pcodec)
    {
        printf("ERROR: could not find encoder for '%s' \n", avcodec_get_name(pOutFmt->video_codec));
        return -1;
    }

    pAVStream = avformat_new_stream(poutFmtCtx, pcodec);
    if (NULL == pAVStream)
    {
       printf("ERROR: could not allocate stream \n");
       return -2;
    }

    pAVStream->id = poutFmtCtx->nb_streams-1;
    PAVCodecCtx = pAVStream->codec;
    vi = pAVStream->index;
    PAVCodecCtx->codec_tag = 0;

    switch ((pcodec)->type)
    {
        case AVMEDIA_TYPE_AUDIO:
            PAVCodecCtx->sample_fmt = (pcodec)->sample_fmts ? (pcodec)->sample_fmts[0] : AV_SAMPLE_FMT_FLTP;
            PAVCodecCtx->bit_rate = 64000;
            PAVCodecCtx->sample_rate = 44100;
            PAVCodecCtx->channels = 2;
            break;

        case AVMEDIA_TYPE_VIDEO:
            PAVCodecCtx->codec_id = AV_CODEC_ID_H264;
            PAVCodecCtx->bit_rate = 0;
            PAVCodecCtx->width = h264_w;
            PAVCodecCtx->height = h264_h;
            PAVCodecCtx->time_base.den = h264_rate*1000;
            PAVCodecCtx->time_base.num = 1000;
            PAVCodecCtx->gop_size = 1;
            PAVCodecCtx->pix_fmt = AV_PIX_FMT_YUV420P;
            if (PAVCodecCtx->codec_id == AV_CODEC_ID_MPEG2VIDEO)
            {
                PAVCodecCtx->max_b_frames = 2;
            }
            if (PAVCodecCtx->codec_id == AV_CODEC_ID_MPEG1VIDEO)
            {
                PAVCodecCtx->mb_decision = 2;
            }
            break;

        default:
            break;
    }

    if (poutFmtCtx->oformat->flags & AVFMT_GLOBALHEADER)
    {
        PAVCodecCtx->flags |= AV_CODEC_FLAG_GLOBAL_HEADER;
    }

    return 0;
}

int write_packet(void * opaque, unsigned char * buf, int buf_size)
{
    if(g_along_event != NULL && g_along_event->synthesis_video != NULL)
    {
        if(g_along_event->synthesis_video_max - g_along_event->synthesis_video_size >= buf_size)
        {
            memcpy(g_along_event->synthesis_video + g_along_event->synthesis_video_size, buf, buf_size);
            g_along_event->synthesis_video_size += buf_size;
        }
        else
        {
            printf("write_packet error, %d, %d\n", g_along_event->synthesis_video_max, g_along_event->synthesis_video_size);
        }
    }
    if(buf_size == 4) // 写文件结尾时,会写四个字节的数据,是数据的长度,需要把改数据加4写进开头第40~44个字节的位置
    {
        g_along_event->synthesis_video[40] = buf[0];
        g_along_event->synthesis_video[41] = buf[1];
        g_along_event->synthesis_video[42] = buf[2];
        g_along_event->synthesis_video[43] = buf[3] + 4;
        if(g_along_event->synthesis_video[43] < 4) // 说明buf[3] + 4大于0XFF,循环了,需要向高位进一
        {
            g_along_event->synthesis_video[42] += 1;
            if(g_along_event->synthesis_video[42] == 0) // 需要向高位进一
            {
                g_along_event->synthesis_video[41] += 1;
                if(g_along_event->synthesis_video[41] == 0) // 需要向高位进一
                {
                    g_along_event->synthesis_video[40] += 1;
                }
            }
        }
    }

    return 0;
}

static int64_t _seek(void *opaque, int64_t offset, int whence)
{
    return 0;
}

int HI_ESCreatMP4(char* p_File_Name, int h264_rate, int h264_w, int h264_h, unsigned char * buf, int b_size)
{
    int ret = 0;
    char pszFileName[256] = {0};
    AVOutputFormat *pOutFmt = NULL;

    if(p_File_Name == NULL || strlen(p_File_Name) <= 0)
    {
        return -1;
    }

    sprintf(pszFileName, "%s", p_File_Name);

    avcodec_register_all();
    av_register_all();
    avformat_network_init();

    //ret = avformat_alloc_output_context2(&g_OutFmt_Ctx, NULL, NULL, pszFileName);
    ret = avformat_alloc_output_context2(&g_OutFmt_Ctx, NULL, "mp4", NULL);
    if(ret < 0)
    {
        printf("avformat_alloc_output_context2 error.\n");
        return -2;
    }

    if (NULL == g_OutFmt_Ctx)
    {   //try default
        printf("ERROR: Could not deduce output format from file extension: using mp4. \n");
        ret = avformat_alloc_output_context2(&g_OutFmt_Ctx, NULL, "mp4", pszFileName);
        if (NULL == g_OutFmt_Ctx || ret < 0)
        {
            printf("avformat_alloc_output_context2 error!, %d\n", ret);
            return -3;
        }
    }

    pOutFmt = g_OutFmt_Ctx->oformat;
    if (pOutFmt->video_codec == AV_CODEC_ID_NONE)
    {
        printf("ERROR: add_stream ID =%d \n",pOutFmt->video_codec);
        goto exit_outFmt_failed;
    }

    ret = HI_PDT_Add_Stream(g_OutFmt_Ctx, h264_rate, h264_w, h264_h);
    if(ret < 0)
    {
        printf("HI_PDT_Add_Stream error!\n");
        goto exit_outFmt_failed;
    }

    av_dump_format(g_OutFmt_Ctx, 0, pszFileName, 1);

    #if 1
    // 输出到MP4文件
    if (!(pOutFmt->flags & AVFMT_NOFILE))
    {
        ret = avio_open(&g_OutFmt_Ctx->pb, pszFileName, AVIO_FLAG_WRITE);
        if (ret < 0)
        {
            printf("ERROR: could not open %s\n", pszFileName);
            goto exit_avio_open_failed;
        }
    }
    #else

    // 输出到内存

        #else
    if(buf != NULL && b_size > 0)
    {
        g_OutFmt_Ctx->pb = avio_alloc_context(buf, b_size, 1, NULL, NULL, write_packet, _seek);//注意:第3个参数赋值为1,否则write_packet回调将不能被成功调用
        g_OutFmt_Ctx->flags |= AVFMT_FLAG_CUSTOM_IO;
    }
    #endif

    /* Write the stream header, if any */
    ret = avformat_write_header(g_OutFmt_Ctx, NULL);
    if (ret < 0)
    {
        printf("Error occurred when opening output file, ret = %d\n", ret);
        goto exit_writeheader_failed;
    }

    return 0;

exit_writeheader_failed:
//exit_avio_open_failed:
    //if (g_OutFmt_Ctx && !(g_OutFmt_Ctx->flags & AVFMT_NOFILE))
    //    avio_close(g_OutFmt_Ctx->pb);

exit_outFmt_failed:
    if(NULL != g_OutFmt_Ctx)
        avformat_free_context(g_OutFmt_Ctx);
    return -4;
}

#define SDC_H264 0
#define SDC_H265 1

int HI_WriteVideo(unsigned char *pstDat, unsigned int DatLen, int enType, int IsKey)
{
    //unsigned int i=0;
    //unsigned char* pPackVirtAddr = NULL;
    //unsigned int u32PackLen = 0;
    //unsigned int u32PackOffset = 0;

    //H264E_NALU_TYPE_E enH264EType;
    //H265E_NALU_TYPE_E enH265EType;

    int ret = 0;
    AVStream * pst = NULL;
    AVPacket pkt;
    //int isIDR = 0;

    if(vi<0)
    {
        printf("HI_WriteVideo vidx %d error!\n");
        return -1;
    }

    if(NULL == pstDat || DatLen <= 0 || enType > 1 || enType < 0)
    {
        printf("HI_WriteVideo ARG Err %p %d %d error!\n", pstDat, DatLen, enType);
        return -2;
    }

    pst = g_OutFmt_Ctx->streams[vi];

    av_init_packet(&pkt);
    pkt.flags |= (IsKey==1) ? AV_PKT_FLAG_KEY : 0;
    pkt.stream_index = pst->index;
    pkt.data = pstDat;
    pkt.size = DatLen;
    pkt.pts = av_rescale_q((ptsInc++), pst->codec->time_base, pst->time_base);
    pkt.dts = av_rescale_q_rnd(pkt.dts, pst->time_base,pst->time_base,(enum AVRounding)(AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX));
    pkt.duration = av_rescale_q(pkt.duration,pst->time_base, pst->time_base);
    pkt.pos = -1;

    ret = av_interleaved_write_frame(g_OutFmt_Ctx, &pkt);
    if (ret < 0)
    {
        printf("av_interleaved_write_frame error, ret: %d\n", ret);
        return -3;
    }

    return 0;
}

void HI_CloseMP4(void)
{
    int ret = 0;

    if(g_OutFmt_Ctx)
    {
        ret = av_write_trailer(g_OutFmt_Ctx);
        if(ret != 0)
        {
            printf("av_write_trailer error, ret = %d\n", ret);
        }
    }

    if(g_OutFmt_Ctx && !(g_OutFmt_Ctx->oformat->flags & AVFMT_NOFILE))
    {
        //ret = avio_close(g_OutFmt_Ctx->pb);
        //if(ret < 0)
        //{
        //    printf("avio_close error, ret = %d\n", ret);
        //}
    }

    if(g_OutFmt_Ctx)
    {
        avformat_free_context(g_OutFmt_Ctx);
        g_OutFmt_Ctx = NULL;
    }
    vi = -1;
    ptsInc = 0;
}

pack_h264.h

#ifndef PACK_H264_H
#define PACK_H264_H

#define EVENT_PRE_VIDEO_S       5
#define EVENT_LATTER_VIDEO_S    3
#define BUF_SIZE_MAX_1080       200 * 1024
#define BUF_SIZE_MAX_2160       1024 * 1024

typedef enum
{
    CAMERA_VENC_FRAME_TYPE_I    = 0,
    CAMERA_VENC_FRAME_TYPE_P    = 1,
    CAMERA_VENC_FRAME_TYPE_B    = 2,
}CAMERA_VENC_FRAME_TYPE;

typedef struct
{
    int              m_Frame_len;  // 帧长度
    int              m_Frame_type;   // 帧类型 I/P/B 帧
    int              m_Rate;         // 帧率
    int              m_Frame_w;  // 帧宽
    int              m_Frame_h;   // 帧高
    int              m_Format;       // (1:H264) or (2:H265)
    int              m_Frame_idx; // 引用次数
    void *           p_Frame_ptr;  // 帧数据
    unsigned int     time_s;         // 获取该帧时的系统时间秒
    unsigned int     time_ms;        // 获取该帧时的系统时间毫秒
    int              m_Rev[2];
    void *           p_Rev[2];
}TS_VENC_FRAME;

typedef struct ITS_ALONG_EVENT_INFO
{
    unsigned int     event_time_s;
    unsigned int     event_time_ms;
    char             along_video_path[128]; // 伴随录像路径  输入 用于ftp上传
    void *           venc_frame[1024]; // 转成TS_VENC_FRAME指针使用  输入
    int              venc_num; // 实际的venc帧数  输入
    unsigned char *  synthesis_video; // 编程MP4文件后的视频  输出
    int              synthesis_video_size; // synthesis_video使用的大小  输出
    int              synthesis_video_max;  // synthesis_video实际的大小  输入
    char             reserve[8];
}ITS_ALONG_EVENT_INFO;
#endif

pack_h264.c

#include <stdio.h>

#include <stdlib.h>

#include <string.h>
#include "pack_h264.h"

int HI_ESCreatMP4(char* p_File_Name, int h264_rate, int h264_w, int h264_h, unsigned char * buf, int b_size);
int HI_WriteVideo(unsigned char *pstDat, unsigned int DatLen, int enType, int IsKey);
void HI_CloseMP4(void);

ITS_ALONG_EVENT_INFO *          g_along_event;  // 用来存储输入参数

static int synthetic_alloc_buf(ITS_ALONG_EVENT_INFO * p_event_info)
{
    int i;
    int size = 0;
    TS_VENC_FRAME * p_Venc_frame = NULL;
    #define VIDEO_BUF_MAX 64 * 1024 // 编码后比编码前大的最大值

    if(p_event_info == NULL)
    {
        return -1;
    }

    for(i = 0; i < p_event_info->venc_num; i++)
    {
        if(p_event_info->venc_frame[i] != NULL)
        {
            p_Venc_frame = (TS_VENC_FRAME *)p_event_info->venc_frame[i];
            size += p_Venc_frame->m_Frame_len;
        }
    }

    if(size <= 0)
    {
        return -2;
    }
    size += VIDEO_BUF_MAX;
    p_event_info->synthesis_video = (unsigned char *)malloc(size);
    if(p_event_info->synthesis_video == NULL)
    {
        return -3;
    }
    memset(p_event_info->synthesis_video, 0, size);
    p_event_info->synthesis_video_max = size;

    return 0;
}

int VencFrame_Free(void * venc_frame)
{
    TS_VENC_FRAME * p_Venc_frame = NULL;

    if(venc_frame == NULL)
    {
        return -1;
    }
    p_Venc_frame = (TS_VENC_FRAME *)venc_frame;
    if(p_Venc_frame->m_Frame_idx > 1)
    {
        p_Venc_frame->m_Frame_idx--;
    }
    else if(p_Venc_frame->m_Frame_idx == 1)
    {
        if(p_Venc_frame->p_Frame_ptr != NULL)
        {
            free(p_Venc_frame->p_Frame_ptr);
            p_Venc_frame->p_Frame_ptr = NULL;
        }
        free(p_Venc_frame);
        p_Venc_frame = NULL;
    }
    else
    {
        printf("m_Frame_idx is error(%d)\n", p_Venc_frame->m_Frame_idx);
    }

    return 0;
}

int main(int argc, char** argv)

{
    int ret = 0,i;
    ITS_ALONG_EVENT_INFO * p_event_info = g_along_event;
    TS_VENC_FRAME * p_Venc_frame = NULL;
    int m_Get_I_Frame = 0;
    unsigned char * enco_buf = NULL;
    int enco_buf_size = 0;

    //假设已经通过另外一个线程,通过时间把需要的裸流h264帧写到了全局变量g_along_event里面

    ret = synthetic_alloc_buf(p_event_info);

    #if 1
    // 输出到MP4文件
    ret = HI_ESCreatMP4("./123.mp4", 30, 768, 576, NULL, 0);
    #else
    // 输出到内存
    char buf[1024 * 10] = {'\0'};
    ret = HI_ESCreatMP4("./123.mp4", 30, 768, 576, buf, sizeof(buf));
    #endif
    if(ret != 0)
    {
        printf("HI_ESCreatMP4 error!\n");
    }

    p_Venc_frame = (TS_VENC_FRAME *)p_event_info->venc_frame[0];

                if(p_Venc_frame->m_Frame_h <= 1080)
                {
                    enco_buf_size = BUF_SIZE_MAX_1080;
                }
                else if(p_Venc_frame->m_Frame_h > 1080 && p_Venc_frame->m_Frame_h <= 2160)
                {
                    enco_buf_size = BUF_SIZE_MAX_1080;
                }
                enco_buf = (unsigned char *)malloc(enco_buf_size);
                if(enco_buf == NULL)
                {
                    printf("ERROR: could not open.\n");
                }
                if(ret != 0)
                {
                    for(i = 0; i < p_event_info->venc_num; i++)
                    {
                        VencFrame_Free(p_event_info->venc_frame[i]);
                        p_event_info->venc_frame[i] = NULL;
                    }
                    if(p_event_info->synthesis_video != NULL)
                    {
                        free(p_event_info->synthesis_video);
                        p_event_info->synthesis_video = NULL;
                    }
                    free(enco_buf);

                }

                for(i = 0; i < p_event_info->venc_num; i++)
                {
                    p_Venc_frame = (TS_VENC_FRAME *)p_event_info->venc_frame[i];
                    if(p_Venc_frame != NULL)
                    {
                        if(CAMERA_VENC_FRAME_TYPE_I == p_Venc_frame->m_Frame_type || m_Get_I_Frame == 1)
                        {
                            m_Get_I_Frame = 1;
                            ret = HI_WriteVideo((unsigned char *)p_Venc_frame->p_Frame_ptr, p_Venc_frame->m_Frame_len, p_Venc_frame->m_Format - 1, 1);
                            if(ret)
                            {
                                printf("%s WriteVideo[%d] Key Frame error, ret: %d\n", __func__, i, ret);
                                HI_CloseMP4();
                                for(; i < p_event_info->venc_num; i++)
                                {
                                    VencFrame_Free(p_event_info->venc_frame[i]);
                                    p_event_info->venc_frame[i] = NULL;
                                }
                                p_event_info->venc_num = 0;
                                break;
                            }
                        }
                        VencFrame_Free(p_Venc_frame);
                        p_event_info->venc_frame[i] = NULL;
                    }
                }
                HI_CloseMP4();
                m_Get_I_Frame = 0;

    return 0;

}

因为数据输入的缘故,该代码还是不能直接运行,需要自己完成数据输入的部分,最终输出的文件在全局变量g_along_event的synthesis_video成员里面,大小为synthesis_video_size。