RV1126_对H265编码数据进行mp4存储

RV1126_对H265编码数据进行mp4存储 Daiker 2024-01-22 13:58:18 804

一、基于FFMPEG的封装
把H264或H265封装成mp4,有很多第三方库可供选择,使用最方便最多的就是FFMPEG。
封装mp4,需要了解各个box结构所包含的信息及作用。所有的数据全部存放在一个个叫box的结构中。
了解各个box的功能,是进行封装的第一步。具体关于mp4各个box功能的介绍,网上可以参考的文章都特别详细。
但是在用FFMPEG进行mp4封装过程中,程序如果中途程序退出,或设备断电,将会导致mp4文件不能正常播放。
原因是mp4封装的与帧相关的长度,偏移位置都记录在最后一个moov Box中,如果该tag记录没有或不完整,播放器就没法读取到视频帧。
通过mp4info程序可以看到一个标准ffmpeg封装的mp4格式各个标签:

其中的moov是封装mp4最关键的Box。
二、避免文件损坏
解决程序中途退出,或设备断电引起的mp4文件不完整,只需要将moov Box和记录视频帧纯数据的mdat Box交换顺序。确保mdat中的数据都是以追加方式写入文件。moov box在固定空间大小内进行修改,每当有视频帧写入,就实时更新该box。不管什么时候断电,都不会对之前的数据造成影响,最多只会影响当前帧的完整性。如下图结构:

三、RV1126编码数据与海思系列的区别
海思是把各个nalu数据分别打包到一个指针数组中,处理起来比较灵活。
RV和海思系列的编码数据不同之处在于RK通过RK_MPI_MB_GetPtr获取到I帧指针后,需要根据nalu_type : 0x20 , 0x21 ,0x22依次去遍历vps,sps,pps。提取出来并放到mp4的moov Box中。为了实现mp4封装代码库的统一,先把RK获取到的编码数据经过函数转换到和海思对应的结构体中,保证底层使用同一套代码。
在调用mp4封装代码前通过以下函数转换:检测各个vps,sps,pps ,返回其对应的地址指针,和对应的长度

```c
static char * find_nalu_tag(char *pu8Buf,int size, int *nalu_len){
int i = 0;
hi_bool bNewPic     = HI_FALSE;
hi_bool bFindStart  = HI_FALSE;
hi_bool bFindEnd    = HI_FALSE;
for(i=0; i < size-4; i++) {
    bNewPic = (pu8Buf[i+0] == 0 && pu8Buf[i+1] == 0 && pu8Buf[i+2] == 0 && pu8Buf[i+3] == 1);
    if (bNewPic) {
        DK_PRT("find 0 0 0 1: type: %02x ", pu8Buf[i+4]);
        bFindStart = HI_TRUE;
        i += 4;
        break;
    }
}
for( ; i < size-4; i++) {
    bNewPic = (pu8Buf[i+0] == 0 && pu8Buf[i+1] == 0 && pu8Buf[i+2] == 0 && pu8Buf[i+3] == 1);
    if (bNewPic) {
        DK_PRT("find 0 0 0 1: type: %02x ", pu8Buf[i+4]);
        bFindEnd = HI_TRUE;
        break;
    }
}
if(bFindStart && bFindEnd){
    *nalu_len  = i;
    return (pu8Buf+i);
}else{
    DK_PRT("find return null ");
    return NULL;
}

}

对其返回值和长度分别对pack进行赋值:

```c
static int deal_frame_stream(venc_stream *pStream,char *ptr,int size,es_VENC_NALU_TYPE_E nalu_type){
    int pack_cnt = 0;
    int i =0;
    int offset = 0;
    int nalu_len = 0;
    char *addr = NULL;
    if(nalu_type ==es_VENC_NALU_IDRSLICE){
        addr = find_nalu_tag(ptr + offset,size-offset,&nalu_len);
        while(addr){
            pStream->pack[pack_cnt].addr = addr;
            pStream->pack[pack_cnt].offset = offset;
            pStream->pack[pack_cnt].len = nalu_len;
            pack_cnt++;
            if(pack_cnt >4){
                DK_PRT("Line:%d find nalu_tag error", __LINE__);
                return -1;
            }
            offset += nalu_len;
            addr = find_nalu_tag(ptr+offset,size-offset,&nalu_len);
        }
    }else if(nalu_type ==es_VENC_NALU_PSLICE){
        pStream->pack[pack_cnt].addr = ptr;
        pStream->pack[pack_cnt].offset = offset;
        pStream->pack[pack_cnt].len = size;
        pack_cnt++;
    }
    pStream->pack_cnt = pack_cnt;
    return 0;
}

四、代码实现
考虑到直接修改ffmpeg源代码工作量比较大,先把封装mp4的功能提取出来,单独实现动态改变moov标签。
感谢ffmpeg开源项目,才使得封装mp4的工作量变小了不少。
把封装mp4的文件做成 动态链接库。
在rkmedia目录下新建目录:third_lib/es2mp4,并把源代码编译成so库文件:libes2mp4.so
在link.txt 中 添加 -L../third_lib/es2mp4/ -les2mp4
在rkmedia/include 新建目录es2mp4
cp rkmedia/third_lib/es2mp4/es2mp4.h ./es2mp4/
在开发板系统中:将编译的libes2mp4.so 拷贝到 /oem/usr/lib下
以下是对封装Box的总体调用过程:


最后封装的mp4文件能正常被系统解析,并播放。
可以看到1000帧的视频,时间总长在33秒,也说明封装信息是正确的。

这里只实现了对视频帧的封装,后续再考虑把音频也封装进mp4,需要考虑到音视频同步的问题。
声明:本文内容由易百纳平台入驻作者撰写,文章观点仅代表作者本人,不代表易百纳立场。如有内容侵权或者其他问题,请联系本站进行删除。
Daiker
红包 5 3 评论 打赏
评论
2个
内容存在敏感词
手气红包
  • 毛巾卷 2024-02-28 14:59:31
    回复
    大佬,详细的过程代码方便发一下学习一下不
    • Daiker 回复 毛巾卷 2024-02-29 17:40:14
      回复
      该方法有其局限性,只能针对特殊场景使用。录像时长受mdat的空间限制。思路其实很简单,mdat和moov互换位置即可,只是动态更新mdat比较麻烦
  • 毛巾卷 2024-02-27 15:08:59
    回复
    学习一下,这个问题我也困扰过
相关专栏
置顶时间设置
结束时间
删除原因
  • 广告/SPAM
  • 恶意灌水
  • 违规内容
  • 文不对题
  • 重复发帖
打赏作者
易百纳技术社区
Daiker
您的支持将鼓励我继续创作!
打赏金额:
¥1易百纳技术社区
¥5易百纳技术社区
¥10易百纳技术社区
¥50易百纳技术社区
¥100易百纳技术社区
支付方式:
微信支付
支付宝支付
易百纳技术社区微信支付
易百纳技术社区
打赏成功!

感谢您的打赏,如若您也想被打赏,可前往 发表专栏 哦~

举报反馈

举报类型

  • 内容涉黄/赌/毒
  • 内容侵权/抄袭
  • 政治相关
  • 涉嫌广告
  • 侮辱谩骂
  • 其他

详细说明

审核成功

发布时间设置
发布时间:
是否关联周任务-专栏模块

审核失败

失败原因
备注
拼手气红包 红包规则
祝福语
恭喜发财,大吉大利!
红包金额
红包最小金额不能低于5元
红包数量
红包数量范围10~50个
余额支付
当前余额:
可前往问答、专栏板块获取收益 去获取
取 消 确 定

小包子的红包

恭喜发财,大吉大利

已领取20/40,共1.6元 红包规则

    易百纳技术社区