使用ffmpeg开源库将h264封装为mp4格式

--------------------- 
作者:Jons伯恩特 
来源:CSDN 
原文:https://blog.csdn.net/huangyifei_1111/article/details/46955021 
版权声明:本文为博主原创文章,转载请附上博文链接!

最近一直在做使用ffmpeg关于读取标准h264格式内存如何封装为mp4格式文件,在经过一周的持续奋战之后在网上找了一些代码,特别的雷神的博客让我获益匪浅,开始不知道如何持续读取发送来的内存块,如何边接受内存边封装为mp4格式文件,找了很多代码也没想出来,后来发现ffmpeg注册的读取拷贝内存的回调函数是自己控制的人为无法控制,经过一番折腾,找了一个比较容易的方法,如果代码有错误的地方希望大家指导,想写一篇博客把自己的代码贴出来。

#include "StdAfx.h"
#include "format.h"


ITAVFormat::ITAVFormat(void)
{
    m_ifmt_ctx = NULL;
    m_ofmt_ctx = NULL;
    m_ofmt       = NULL;
    m_bufAvalloc = NULL;
    m_fileName = NULL;
    for(int i=0; i<BUF_UNIT_NUM; i++)
    {
        m_bufDeq.data[i].ptr = new unsigned char[UNIT_SIZE];
        memset(m_bufDeq.data[i].ptr,0,UNIT_SIZE);
        m_bufDeq.data[i].size = 0;
        m_bufDeq.data[i].readsize = 0;
    }
    m_bufDeq.head = 0;
    m_bufDeq.tail = 0;
    m_countFill = 0;
    m_frame_index = 0;
    m_startCode = 0;
}


ITAVFormat::~ITAVFormat(void)
{
    for(int i=0; i<BUF_UNIT_NUM; i++)
    {
        delete []m_bufDeq.data[i].ptr;
        m_bufDeq.data[i].size = 0;
        m_bufDeq.data[i].readsize = 0;
    }
    m_bufDeq.head = 0;
    m_bufDeq.tail = 0;
}

int ITAVFormat::read_data(void *opaque, uint8_t *buf, int buf_size)
{
    //printf("readData!!!!!!!!!!!!\n");
    ITAVFormat* pThis = (ITAVFormat*)opaque;
    if(pThis->m_bufDeq.head == pThis->m_bufDeq.tail)
    {
        //printf("*********head==tail********buf_size=%ld\n",buf_size);
        return 0;
    }
    else
    {
        int head = pThis->m_bufDeq.head;
        buf_size = FFMIN(buf_size, pThis->m_bufDeq.data[head].size);
        printf("ptr:%p,size:%u,buf_size=%u\n",pThis->m_bufDeq.data[head].ptr, pThis->m_bufDeq.data[head].size,buf_size);

        /* copy internal buffer data to buf */
        memcpy(buf, pThis->m_bufDeq.data[head].ptr+pThis->m_bufDeq.data[head].readsize, buf_size);

        pThis->m_bufDeq.data[head].readsize  += buf_size;
        pThis->m_bufDeq.data[head].size -= buf_size;

        if(pThis->m_bufDeq.data[head].size <= 0)
        {
            pThis->m_bufDeq.data[head].readsize = 0;
            pThis->m_bufDeq.head = (pThis->m_bufDeq.head+1)%BUF_UNIT_NUM;

        }
        return buf_size;
    }
    
}
void ITAVFormat::init()
{
    av_register_all();
}

int ITAVFormat::fillBuffer(unsigned char* buff,int len)
{
    //buffer_data b
    if(buff == NULL)
        return TRUE;
    if(len > UNIT_SIZE)
        return FALSE;
    int tail = m_bufDeq.tail;
    memcpy(m_bufDeq.data[tail].ptr,buff,len);
    m_bufDeq.data[tail].size = len;
    m_bufDeq.data[tail].readsize = 0;
    m_bufDeq.tail = (m_bufDeq.tail+1)%BUF_UNIT_NUM;
    
    if(m_countFill == 0)
    {
    
        m_startCode = start();
        if(m_startCode < 0)
            return FALSE;
        m_countFill++;    
    }
    else
        writeFrame();
    
    return TRUE;
}

int ITAVFormat::writeFrame()
{
    int ret = -1;
    AVPacket pkt;
    while(1) 
    {
        AVStream *in_stream, *out_stream;
        //Get an AVPacket
        ret = av_read_frame(m_ifmt_ctx, &pkt);
        //printf("ret=%d\n",ret);
        if (ret < 0)
        {
            printf("ret = %d\n",ret);
            break;
        }
        in_stream  = m_ifmt_ctx->streams[pkt.stream_index];
        out_stream = m_ofmt_ctx->streams[pkt.stream_index];

        ///如果没有显示时间戳自己加上时间戳并且将显示时间戳赋值给解码时间戳
        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)(m_frame_index*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);
            m_frame_index++;
        }

        //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.pos = -1;
        //Write
        if (av_interleaved_write_frame(m_ofmt_ctx, &pkt) < 0) {
            printf( "Error muxing packet\n");
            break;
        }
        //printf("Write %8d frames to output file\n",frame_index);
        av_free_packet(&pkt);
        m_frame_index++;
    }
     return TRUE;
}
// av_probe_input_buffer2
//     AVPROBE_SCORE_MAX
int ITAVFormat::start()
{
    int ret,i;
    AVIOContext* pb = NULL;
    AVInputFormat* piFmt = NULL;
    m_bufAvalloc = (unsigned char*)av_malloc(32768);
    pb = avio_alloc_context(m_bufAvalloc, 32768, 0, (void*)this, ITAVFormat::read_data, NULL, NULL);

    if (av_probe_input_buffer(pb, &piFmt, "", NULL, 0, 0) < 0)
        return -1;
    else{
        printf("format:%s[%s]\n", piFmt->name, piFmt->long_name);
    }
    m_ifmt_ctx = avformat_alloc_context();
    m_ifmt_ctx->pb = pb;

    //Input
    if (avformat_open_input(&m_ifmt_ctx, "", piFmt, NULL) != 0){//iformat,priv_data赋值,pb, nbstreams,streams为null
        printf("Couldn't open input stream.(无法打开输入流)\n");
        return -2;
    }

    if ((ret = avformat_find_stream_info(m_ifmt_ctx, 0)) < 0) {
        printf( "Failed to retrieve input stream information");
        return -3;
    }
    //Output
    avformat_alloc_output_context2(&m_ofmt_ctx, NULL, NULL, m_fileName);
    if (!m_ofmt_ctx) {
        printf( "Could not create output context\n");
        return -4;
        //goto end;
    }
    m_ofmt = m_ofmt_ctx->oformat;
    for (i = 0; i < m_ifmt_ctx->nb_streams; i++) {
        //Create output AVStream according to input AVStream
        AVStream *in_stream = m_ifmt_ctx->streams[i];
        AVStream *out_stream = avformat_new_stream(m_ofmt_ctx, in_stream->codec->codec);
        if (!out_stream) {
            printf( "Failed allocating output stream\n");
            return -5;
        }
        //Copy the settings of AVCodecContext
        if (avcodec_copy_context(out_stream->codec, in_stream->codec) < 0) {
            printf( "Failed to copy context from input to output stream codec context\n");
            return -6;
        }
        out_stream->codec->codec_tag = 0;
        if (m_ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
        {
            out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
        }
    }
    //Output information------------------
    av_dump_format(m_ofmt_ctx, 0, m_fileName, 1);
    //Open output file
    if (!(m_ofmt->flags & AVFMT_NOFILE)) {
        ret = avio_open(&m_ofmt_ctx->pb, m_fileName, AVIO_FLAG_WRITE);
        if (ret < 0) {
            printf( "Could not open output file '%s'", m_fileName);
            return -7;
        }
    }
    //Write file header
    if (avformat_write_header(m_ofmt_ctx, NULL) < 0) { 
        printf( "Error occurred when opening output file\n");
        return -8;
    }
    return TRUE;
}

void ITAVFormat::setFileName(char* fileName)
{
    m_fileName = fileName;
}

void ITAVFormat::finish()
{
    if(m_startCode < 0)
        return;
    //Write file trailer
    av_write_trailer(m_ofmt_ctx);
    //av_free(m_bufAvalloc);
    avformat_close_input(&m_ifmt_ctx);
    /* close output */
    if (m_ofmt_ctx && !(m_ofmt->flags & AVFMT_NOFILE))
        avio_close(m_ofmt_ctx->pb);
    avformat_free_context(m_ofmt_ctx);
    //av_free(m_bufAvalloc);
}

 

H.264 封装MP4 的源码可以比较复杂,需要涉及到视频编码、封装格式等多个方面的知识。以下是一个简单的示例代码,可以实现将 H.264 裸流封装MP4 文件的功能: ```c #include <stdio.h> #include <stdlib.h> #include <string.h> #define FRAME_RATE 25 #define BIT_RATE 4800000 typedef struct { FILE *fp; int width; int height; int frame_rate; int bit_rate; int gop_size; int frame_count; } MP4File; typedef struct { unsigned char *data; int size; int key_frame; int pts; } H264Frame; static void write_int(FILE *fp, int value) { unsigned char buf[4]; buf[0] = value >> 24; buf[1] = value >> 16; buf[2] = value >> 8; buf[3] = value; fwrite(buf, 1, 4, fp); } static void write_short(FILE *fp, short value) { unsigned char buf[2]; buf[0] = value >> 8; buf[1] = value; fwrite(buf, 1, 2, fp); } static void write_data(FILE *fp, unsigned char *data, int size) { fwrite(data, 1, size, fp); } MP4File *mp4_create(const char *filename, int width, int height, int frame_rate, int bit_rate) { MP4File *mp4 = (MP4File *)malloc(sizeof(MP4File)); memset(mp4, 0, sizeof(MP4File)); mp4->fp = fopen(filename, "wb"); if (!mp4->fp) { printf("failed to create mp4 file\n"); free(mp4); return NULL; } mp4->width = width; mp4->height = height; mp4->frame_rate = frame_rate; mp4->bit_rate = bit_rate; mp4->gop_size = frame_rate * 2; write_data(mp4->fp, (unsigned char *)"ftypisom", 8); write_int(mp4->fp, 0); write_data(mp4->fp, (unsigned char *)"isom", 4); write_data(mp4->fp, (unsigned char *)"mp42", 4); return mp4; } void mp4_destroy(MP4File *mp4) { if (mp4) { fclose(mp4->fp); free(mp4); } } void mp4_write_frame(MP4File *mp4, H264Frame *frame) { if (!mp4 || !frame) { return; } if (frame->key_frame) { write_data(mp4->fp, (unsigned char *)"mdat", 4); write_int(mp4->fp, 0); } int size = frame->size + 4; write_int(mp4->fp, size); if (frame->key_frame) { write_data(mp4->fp, (unsigned char *)"avc1", 4); write_short(mp4->fp, 0); write_short(mp4->fp, 0); write_short(mp4->fp, mp4->width); write_short(mp4->fp, mp4->height); write_int(mp4->fp, mp4->bit_rate); write_int(mp4->fp, mp4->frame_rate); write_short(mp4->fp, 1); write_short(mp4->fp, 12); write_data(mp4->fp, (unsigned char *)"avcC", 4); write_int(mp4->fp, 0x01); write_data(mp4->fp, frame->data + 4, 3); write_short(mp4->fp, 0xffe1); write_short(mp4->fp, 16); write_data(mp4->fp, (unsigned char *)"avcC", 4); write_data(mp4->fp, frame->data + 7, frame->size - 7); } else { write_data(mp4->fp, frame->data, frame->size); } mp4->frame_count++; } int main() { MP4File *mp4 = mp4_create("test.mp4", 1280, 720, FRAME_RATE, BIT_RATE); if (!mp4) { return 0; } FILE *h264 = fopen("test.h264", "rb"); if (!h264) { mp4_destroy(mp4); return 0; } H264Frame frame; memset(&frame, 0, sizeof(H264Frame)); frame.data = (unsigned char *)malloc(1024 * 1024); while (!feof(h264)) { size_t size = fread(frame.data, 1, 1024 * 1024, h264); frame.size = size; frame.key_frame = 1; frame.pts = mp4->frame_count * 90000 / mp4->frame_rate; mp4_write_frame(mp4, &frame); } free(frame.data); fclose(h264); mp4_destroy(mp4); return 0; } ``` 在这个示例中,我们首先定义了一个 MP4File 结构体,用于保存 MP4 文件的相关信息,比如宽高、帧率、码率等。mp4_create 函数用于创建 MP4 文件,它会写入 MP4 文件的头信息。mp4_write_frame 函数用于写入 H.264 数据帧,它会根据帧类型(关键帧或非关键帧)来写入相应的 MP4 Box。最后,在主函数中我们读入 H.264 裸流,并将其封装MP4 文件。 需要注意的是,这里只是一个简单的示例,实际的 MP4 封装格式可能比这个复杂得多,还需要考虑音频、时间戳等问题。如果需要进行更加复杂的封装,建议使用现有的一些开源封装,比如 FFmpeg 等。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值