mp4v2 写mp4 java_使用mp4v2将H264+AAC合成mp4文件

录制程序要添加新功能:录制CMMB电视节目,我们的板卡发送出来的是RTP流(H264视频和AAC音频),录制程序要做的工作是:

(1)接收并解析RTP包,分离出H264和AAC数据流;

(2)将H264视频和AAC音频以某种格式封装,最后存成文件,供用户查看。

第一步已经有部分代码可供参考,因此很快就完成了。

第二步,我们决定封装成mp4,查找了一些资料后,决定使用开源库mp4v2来合成mp4文件。

技术路线已确定,就开工干活。

(一)mp4格式的基础知识。

关于mp4格式,网上介绍的不少,有以下内容可供参考:

(1)两个ISO标准:

[ISO/IEC 14496-12]:ISO base media file format --”is a general format forming the basis for a number of other more specific file formats. This format contains the timing, structure, and media information for timed sequences of media data, such as audio-visual presentations ”

[ISO/IEC 14496-14]:MP4 file format --”This specification defines MP4 as an instance of the ISO Media File format [ISO/IEC 14496-12 and ISO/IEC

15444-12]. ”

定义了mp4文件格式标准。

是上面两个标准的解释,建议先看这个,了解大概,具体细节再看ISO标准文件。

(二)技术验证。主要就是写验证代码,验证技术可行性。

去官网下载mp4v2源码、编译、安装过程略过不提。所有资料可以在http://code.google.com/p/mp4v2/找到。

先写部分验证代码,很快完成了,但封装出来的文件有问题,无法播放。

合成部分,代码如下:

static void* writeThread(void* arg)

{

rtp_s* p_rtp = (rtp_s*) arg;

if (p_rtp == NULL)

{

printf("ERROR!\n");

return;

}

MP4FileHandle file = MP4CreateEx("test.mp4", MP4_DETAILS_ALL, 0, 1, 1, 0, 0, 0, 0);

if (file == MP4_INVALID_FILE_HANDLE)

{

printf("open file fialed.\n");

return;

}

MP4SetTimeScale(file, 90000);

//添加h264 track

MP4TrackId video = MP4AddH264VideoTrack(file, 90000, 90000 / 25, 320, 240,

0x64, //sps[1] AVCProfileIndication

0x00, //sps[2] profile_compat

0x1f, //sps[3] AVCLevelIndication

3); // 4 bytes length before each NAL unit

if (video == MP4_INVALID_TRACK_ID)

{

printf("add video track failed.\n");

return;

}

MP4SetVideoProfileLevel(file, 0x7F);

//添加aac音频

MP4TrackId audio = MP4AddAudioTrack(file, 48000, 1024, MP4_MPEG4_AUDIO_TYPE);

if (video == MP4_INVALID_TRACK_ID)

{

printf("add audio track failed.\n");

return;

}

MP4SetAudioProfileLevel(file, 0x2);

int ncount = 0;

while (1)

{

frame_t* pf = NULL; //frame

pthread_mutex_lock(&p_rtp->mutex);

pf = p_rtp->p_frame_header;

if (pf != NULL)

{

if (pf->i_type == 1)//video

{

MP4WriteSample(file, video, pf->p_frame, pf->i_frame_size, MP4_INVALID_DURATION, 0, 1);

}

else if (pf->i_type == 2)//audio

{

MP4WriteSample(file, audio, pf->p_frame, pf->i_frame_size , MP4_INVALID_DURATION, 0, 1);

}

ncount++;

//clear frame.

p_rtp->i_buf_num--;

p_rtp->p_frame_header = pf->p_next;

if (p_rtp->i_buf_num <= 0)

{

p_rtp->p_frame_buf = p_rtp->p_frame_header;

}

free_frame(&pf);

pf = NULL;

if (ncount >= 1000)

{

break;

}

}

else

{

//printf("BUFF EMPTY, p_rtp->i_buf_num:%d\n", p_rtp->i_buf_num);

}

pthread_mutex_unlock(&p_rtp->mutex);

usleep(10000);

}

MP4Close(file);

}

现象:没有图像,也没有声音,根本无法播放。

于是,艰苦的工作开始了:跟踪查找原因。

(1)使用 vlc播放合成的mp4文件,查看详细输出:

vlc -vvv test.mp4

[0x8e9357c] mp4 stream debug: found Box: ftyp size 24

[0x8e9357c] mp4 stream debug: found Box: free size 136

[0x8e9357c] mp4 stream debug: skip box: "free"

[0x8e9357c] mp4 stream debug: found Box: mdat size 985725

[0x8e9357c] mp4 stream debug: skip box: "mdat"

[0x8e9357c] mp4 stream debug: found Box: moov size 5187

[0x8e9357c] mp4 stream debug: found Box: mvhd size 108

[0x8e9357c] mp4 stream debug: read box: "mvhd" creation

734515d-06h:22m:03s modification 734515d-06h:22m:23s

time scale 90000 duration 694977d-48h:00m:29s

rate 1.000000 volume 1.000000 next track id 3

可以看到vlc(实际上是调用libmp4库)解析box都正确的,mdat的大小也是正确的。

但接下来一行:

skip box: "mdat"

就比较奇怪了,明明解析正确了,为什么要将mdat忽略掉呢?要知道,mdat里存放的可是真正的音视频数据阿?如果skip掉了,后面解码时没有数据,当然播放不了了?

(2)既然找到疑点,继续跟踪。

查看vlc的源代码,在文件modules/demux/mp4/libmp4.c中发现:skip信息是由MP4_ReadBoxSkip()函数打印的,而调用的地方在libmp4.c中2641行:

/* Nothing to do with this box */

{ FOURCC_mdat,  MP4_ReadBoxSkip,        MP4_FreeBox_Common },

{ FOURCC_skip,  MP4_ReadBoxSkip,        MP4_FreeBox_Common },

{ FOURCC_free,  MP4_ReadBoxSkip,        MP4_FreeBox_Common },

{ FOURCC_wide,  MP4_ReadBoxSkip,        MP4_FreeBox_Common },

而在libmp4.h中:

#define FOURCC_mdat VLC_FOURCC( 'm', 'd', 'a', 't' )

#define FOURCC_skip VLC_FOURCC( 's', 'k', 'i', 'p' )

#define FOURCC_free VLC_FOURCC( 'f', 'r', 'e', 'e' )

#define FOURCC_wide VLC_FOURCC( 'w', 'i', 'd', 'e' )

从代码看,vlc调用libmp4解析文件时,主动忽略了mdat,skip,free,wide这四种类型的box。

为什么呢?

(3)继续查看modules/demux/mp4/mp4.c中的Open()函数(解析模块的入口函数),可以看到本模块的主要工作是初始化一个demux_sys_t结构体,该结构体定义如下:

struct demux_sys_t

{

MP4_Box_t    *p_root; /* container for the whole file */

mtime_t      i_pcr;

uint64_t     i_time; /*time position of the presentation * in movie timescale*/

uint64_t     i_timescale;    /* movie time scale */

uint64_t     i_duration;     /* movie duration */

unsigned int i_tracks;       /* number of tracks */

mp4_track_t  *track;/* array of track */

float        f_fps; /* number of frame per seconds */

/* */

MP4_Box_t    *p_tref_chap;

/* */

input_title_t *p_title;

};

似乎只是为了获取mp4的tracks,moov,duration, timescale等基本信息,实际上并不解码数据,因此就不需要关注mdat这个box了。

综上:vlc的输出是正常的,libmp4忽略了mdat这个box也不是造成mp4文件无法播放的原因,只是因为libmp4这个模块并不真正解码数据,所以不需要关注这个box。

既然问题不在这,那在哪里呢?

(4)继续看vlc的输出:

AVC: nal size -1710483062

no frame!

[0x8e93eb4] avcodec decoder warning: cannot decode one frame (3595 bytes)

可以看到,vlc实际上是调用avcodec(ffmpeg)来解码数据的,我们的视频是AVC(H264)格式的。

从错误信息可以确定,是H264的NAL大小错误,似乎跟mp4文件本身关系不大。

不管那么多,先看看代码再说。

vlc是以lib的形式使用ffmpeg的,所以我们必须看ffmpeg的代码:

libavcodec/h264.c:

static int decode_nal_units(H264Context *h, const uint8_t *buf, int buf_size){

….

for(;;){

if(buf_index >= next_avc) {

if(buf_index >= buf_size) break;

nalsize = 0;

for(i = 0; i nal_length_size; i++)

nalsize = (nalsize <

if(nalsize <= 0 || nalsize > buf_size - buf_index){

av_log(h->s.avctx, AV_LOG_ERROR, "AVC: nal size %d\n", nalsize);

break;

}

next_avc= buf_index + nalsize;

}

}

可以看到,正是这里报错的。

但是,为什么报错呢?根据ffmpeg的信息,知道取出来的 nalsize为负数。

怀疑是h264流本身有问题,于是用Elecard查看了生成的mp4文件,视频播放又非常正常。似乎h264流是正常的?

愁呀愁。。。。

内容如下:

Ottavio Campana

“question about MP4AddH264VideoTrack。

What's the meaning of the profile_compat and

sampleLenFieldSizeMinusOne fields?”

Jeremy Noring

"Usually an NALU is prefixed by the start code 0x00000001. To write it

as a sample in MP4 file format, just replace the start code with size

of the NALU(without 4-byte start code) in big endian. You also need to

specify how many bytes of the size value requires. Take libmp4v2 for

example, the last parameter in MP4AddH264VideoTrack(.., uint8_t

sampleLenFieldSizeMinusOne) indicate the number of byes minus one."

...so each sample you and to mp4v2 should be prefixed with a size code

(in big-endian, of course). I use a 4 byte size code, so

sampleLenFieldSizeMinusOne gets set to 3. This seems to work; my

files playback on just about everything. Perhaps one of the project

maintainers can clarify this, and it'd also be good to update the

documentation of that call to make this clear.”

Ottavio Campana

that's the code I used as reference to write my program :-(

but my doubt is that there must be something wrong somewhere, because

boxes seem to be correctly written, but when I try to decode them I

get errors like

[h264 @ 0xb40fa0]AVC: nal size -502662121

have you ever seen an error like this?

Ottavio Campana

> Not sure, but it looks you're not converting it to big-endian before

> prefixing it to your sample.

well, eventually using ffmpeg to dump the read frames, I discovered

that I had to strip che NALU start code, i.e. the 0x00000001, and to

put the NALU size at its place.

It works perfectly now, but I still wonder why I had to put the size

at the begin of the data, since it is a parameter which is passed to

MP4WriteSample, so I expected the function to add it.

从中得到如下关键信息:

(1)h264流中的NAL,头四个字节是0x00000001;

(2)mp4中的h264track,头四个字节要求是NAL的长度,并且是大端顺序;

(3)mp4v2很可能针对此种情况并没有做处理,所以写到mp4文件中的每个NAL头四个字节还是0x00000001.

那好说,我将每个sample(也就是NAL)的头四个字节内容改成NAL的长度,且试试看:

if(pf->i_frame_size >= 4)

{

uint32_t* p = (&pf->p_frame[0]);

*p = htonl(pf->i_frame_size -4);//大端,去掉头部四个字节

}

MP4WriteSample(file, video, pf->p_frame, pf->i_frame_size,     MP4_INVALID_DURATION, 0, 1);

测试下来,果然OK了!

(6)视频已经解决了,音频还有问题:播放的声音太快。

尝试调整参数:

MP4TrackId audio = MP4AddAudioTrack(file, 48000, 1024, MP4_MPEG4_AUDIO_TYPE);

第三个参数sampleDuration,表示每个sample持续多少个duration,网上看到的都是1024。

我尝试了几个不同的值:128,256,512,4096都不行,最后发现设为2048就正常了。

(为什么是2048??????我不清楚,也许是因为我们的音频是双声道?有时间再研究。。。)

正确代码如下:

MP4TrackId audio = MP4AddAudioTrack(file, 48000, 2048, MP4_MPEG4_AUDIO_TYPE);

至此,已经成功的将rtp流合成了mp4文件,证明了技术上是可行的。

注意:很多参数都是针对我们的具体应用写死的,仅供参考。

(三)将功能合并到录制程序中。

略。

from:http://www.rosoo.net/a/201305/16631.html

本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若转载,请注明出处:http://www.mzph.cn/news/443080.shtml

如若内容造成侵权/违法违规/事实不符,请联系多彩编程网进行投诉反馈email:809451989@qq.com,一经查实,立即删除!

相关文章

java完全解耦_java-完全解耦

完全解耦&#xff1a;降低代码的限制性&#xff0c;是同一代码能够用到更多的程序中1 packageinterfaces.interfaceprocessor;2 import staticnet.mindview.util.Print.print;34 interfaceProcessor {5 String name();6 Object process(Object input);7 }8 public classApply{9…

java验证xml格式是否正确的是_spring源码附录(1)java实现对XML格式的验证

最近在看spring源码&#xff0c;涉及到xml文档的解析、xml文档的格式验证&#xff0c;发现自己对xml解析的基础较为薄弱&#xff0c;本篇博客复习下DOM方式解析xml(即spring解析xml的方式)。DOM解析XML是将整个XML作为一个对象&#xff0c;占用内存较多。另外一个java官方的XML…

java 批量打印_JAVA批量打印皕杰报表

原标题&#xff1a;JAVA批量打印皕杰报表不使用皕杰报表工具条上的打印按钮&#xff0c;用java怎么实现批量打印皕杰报表呢&#xff1f;解决方案&#xff1a;皕杰报表提供了批量打印的工具类ReportToolkits&#xff0c;ReportToolkits类参考帮助文档-开发指南-javadoc-bios.rep…

java 截串_java字符串截取

import org.apache.commons.lang.stringutils;public class substr{public static void main(string[] args) {string str "1234567890abcdefg";system.out.println("-----------" str.substring(0)); //从字符串索引为0开始截取&#xff0c;一直到字符串…

java json 构造_json 构造和解析

目录&#xff1a;(1)引入jar包&#xff1b;(2)json的构造&#xff1b;(3)json的解析&#xff1b;(4)遍历未知key。(1)java对json的处理&#xff0c;可借助org.json.jar.org.jsonjson20090211(2)json的构造//construct json and output itpublic String constructJson() throws …

java http 上传文件_java利用httpClient实现后台文件上传请求

之前写过基于html和js的文件上传方法java 用springMVC 和HttpServletRequest 两种实现文件上传的方法和httpClient后台执行普通post请求的文章java通过httpClient从接口请求数据入库以及自动生成实体工具类&#xff0c;最近接到一个需求&#xff0c;需要用到后台去调用远程服务…

morse java_华威MORSE,华威数统那个比较好?

其实没什么高下之分了。 毕竟大家都在一个系&#xff0c;上下课交作业都在一起。我个人觉得&#xff0c;数统要比morse更flexible一些。首先你要明白morse和数统课程上的区别在哪里:数统 大一必修数统大一必修共计84CATSmorse大一必修morse大一必修共计120CATS其实差的就是EC10…

java中 以下接口以键_java复习题

1&#xff0e;Java中的long类型占用()个字节。A、1B、2C、4D、82&#xff0e;以下关于继承的叙述不正确的是()。A、在Java中类只允许单一继承B、在Java中一个类只能实现一个接口C、在Java中一个类可以同时继承一个类和实现一个接口D、在Java中接口允许多继承3&#xff0e;4&…

Java游戏有易筋经_易筋经- JavaWeb-1

JavaScript一种直译式脚本语言&#xff0c;是一种动态类型、弱类型、基于原型的语言&#xff0c;内置支持类型。它的解释器被称为JavaScript引擎&#xff0c;为浏览器的一部分&#xff0c;广泛用于客户端的脚本语言组成部分:ECMAScript:js基础语法(规定 关键字 运算符 语句 函数…

java导出hbase表数据_通用MapReduce程序复制HBase表数据

编写MR程序&#xff0c;让其可以适合大部分的HBase表数据导入到HBase表数据。其中包括可以设置版本数、可以设置输入表的列导入设置(选取其中某几列)、可以设置输出表的列导出设置(选取其中某几列)。原始表test1数据如下&#xff1a;每个row key都有两个版本的数据&#xff0c;…

java双语试卷_Java程序设计基础(双语)试题题目及答案,课程2021最新期末考试题库,章节测验答案...

若二项式(x&#xff0b;13x)n的展开式中含3x的项是第三项&#xff0c;则n的值是______&#xff0e;(x2&#xff0b;1ax)6(a&#xff1e;0)的展开式中常数项是15&#xff0c;那么展开式中所有项系数和是______&#xff0e;(x2&#xff0b;1ax)6(a&#xff1e;0)的展开式中常数项…

java服务器和linux_在Linux下开一个Java服务器(使用CatServer Pro)

引言Linux开服具有快速&#xff0c;高效&#xff0c;性能等特点&#xff0c;而Windows虽然简单&#xff0c;但是不具备Linux良好的性能。本教程就说明一下简单的Linux开服方式(需要教程的人&#xff0c;如果你学会后&#xff0c;请无偿帮助更多的人。)服务器准备首先。先准备一…

java中js九个隐含对象_第九章 JSP标签——《跟我学Shiro》

Shiro提供了JSTL标签用于在JSP/GSP页面进行权限控制&#xff0c;如根据登录用户显示相应的页面按钮。导入标签库标签库定义在shiro-web.jar包下的META-INF/shiro.tld中定义。guest标签欢迎游客访问&#xff0c;登录用户没有身份验证时显示相应信息&#xff0c;即游客访问信息。…

java中jsp标准动作_JavaBean和jsp标准动作

一.JavaBean 1.理解&#xff1a;可以重用的java类 2.分类 1)封装数据的bean(相当于实体类) 2)封装业务的bean(一般就是实现增删改查) 3.注意&#xff1a; 1)封装数据的bean一般要满足如下两个条件 1.implements Serializable&#xff1a;实现序列化接口 2.拥有一个无参的public…

我的世界java版游戏崩溃_我的世界全攻略之-游戏崩溃的解决方法

我的世界崩溃怎么办&#xff1f;下面吾爱网小编给大家带来我的世界无法正常启动的解决方法,需要的朋友可以参考下。我的世界作为许多玩家都十分喜爱的模拟经营沙盘类游戏,经常有玩家反映在玩我的世界的时候,游戏总是会出现崩溃或者无法启动的情况,玩家在遇到的时候不知道怎么办…

手写实现java中的trim_JS中字符串trim()使用示例

示例一&#xff1a;测试JS扩展方法// 合并多个空白为一个空白String.prototype.ResetBlank function() { //对字符串扩展var regEx /\s/g;return this.replace(regEx, );};window.onload function(){var str "你 在他想还好吗?";alert(str);str str.ResetBlan…

java excil表格开发_JAVA导出Excel电子表格的方法

JAVA导出Excel电子表格的方法package com.qingruxu.excel;import java.io.File;import java.io.IOException;import java.net.URL;import jxl.Sheet;import jxl.Workbook;import jxl.format.Border;import jxl.read.biff.BiffException;import jxl.write.Blank;import jxl.writ…

vue3 新项目 - 搭建路由router

创建router/index 文件 main.ts 安装 router 然后 在 app下面 去 设置 路由出口

java json中的注释_如何使用Java中的@Expose注释从JSON中排除字段?

Gson Expose批注可用于标记要公开或不公开(串行化或反序列化)的字段。 expose注释可以取两个参数和每个参数是可以采取任一值的布尔真或假。为了使GSON对Expose批注做出反应&#xff0c;我们必须使用GsonBuilder类创建一个Gson实例&#xff0c;并需要调用excludeFieldsWithoutE…

java 屏蔽邮箱_使用javamail发送邮件的时候如何阻止附件内容输出到控制台

我在使用JavaMail发送带附件的邮件时候&#xff0c;每次到了Transport.sendMessage()这一步&#xff0c;控制台就会输出附件内容&#xff0c;请问如何设置可以取消输出呢&#xff1f;public void sendFileAttachedMail(String fromMail, String toMail, String fromMailPwd, St…