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.pswp.cn/news/443080.shtml
繁體地址,請注明出處:http://hk.pswp.cn/news/443080.shtml
英文地址,請注明出處:http://en.pswp.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…