录制程序要添加新功能:录制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 定义了mp4文件格式标准。 (2)http://wenku.baidu.com/view/673482284b73f242336c5f4c.html 是上面两个标准的解释,建议先看这个,了解大概,具体细节再看ISO标准文件。
(二)技术验证。主要就是写验证代码,验证技术可行性。 去官网下载mp4v2源码、编译、安装过程略过不提。所有资料可以在http://code.google.com/p/mp4v2/找到。 先写部分验证代码,很快完成了,但封装出来的文件有问题,无法播放。
合成部分,代码如下: 1 static void* writeThread(void* arg) 2 { 3 rtp_s* p_rtp = (rtp_s*) arg; 4 if (p_rtp == NULL) 5 { 6 printf("ERROR!\n"); 7 return; 8 } 9 10 MP4FileHandle file = MP4CreateEx("test.mp4", MP4_DETAILS_ALL, 0, 1, 1, 0, 0, 0, 0);//创建mp4文件 11 if (file == MP4_INVALID_FILE_HANDLE) 12 { 13 printf("open file fialed.\n"); 14 return; 15 } 16 17 MP4SetTimeScale(file, 90000); 18 19 //添加h264 track 20 MP4TrackId video = MP4AddH264VideoTrack(file, 90000, 90000 / 25, 320, 240, 21 0x64, //sps[1] AVCProfileIndication 22 0x00, //sps[2] profile_compat 23 0x1f, //sps[3] AVCLevelIndication 24 3); // 4 bytes length before each NAL unit 25 if (video == MP4_INVALID_TRACK_ID) 26 { 27 printf("add video track failed.\n"); 28 return; 29 } 30 MP4SetVideoProfileLevel(file, 0x7F); 31 32 //添加aac音频 33 MP4TrackId audio = MP4AddAudioTrack(file, 48000, 1024, MP4_MPEG4_AUDIO_TYPE); 34 if (video == MP4_INVALID_TRACK_ID) 35 { 36 printf("add audio track failed.\n"); 37 return; 38 } 39 MP4SetAudioProfileLevel(file, 0x2); 40 41 42 int ncount = 0; 43 while (1) 44 { 45 frame_t* pf = NULL; //frame 46 pthread_mutex_lock(&p_rtp->mutex); 47 pf = p_rtp->p_frame_header; 48 if (pf != NULL) 49 { 50 if (pf->i_type == 1)//video 51 { 52 MP4WriteSample(file, video, pf->p_frame, pf->i_frame_size, MP4_INVALID_DURATION, 0, 1); 53 } 54 else if (pf->i_type == 2)//audio 55 { 56 MP4WriteSample(file, audio, pf->p_frame, pf->i_frame_size , MP4_INVALID_DURATION, 0, 1); 57 } 58 59 ncount++; 60 61 //clear frame. 62 p_rtp->i_buf_num--; 63 p_rtp->p_frame_header = pf->p_next; 64 if (p_rtp->i_buf_num <= 0) 65 { 66 p_rtp->p_frame_buf = p_rtp->p_frame_header; 67 } 68 free_frame(&pf); 69 pf = NULL; 70 71 if (ncount >= 1000) 72 { 73 break; 74 } 75 } 76 else 77 { 78 //printf("BUFF EMPTY, p_rtp->i_buf_num:%d\n", p_rtp->i_buf_num); 79 } 80 pthread_mutex_unlock(&p_rtp->mutex); 81 usleep(10000); 82 } 83 84 MP4Close(file); 85 }
现象:没有图像,也没有声音,根本无法播放。 于是,艰苦的工作开始了:跟踪查找原因。
(1)使用 vlc播放合成的mp4文件,查看详细输出: 1 vlc -vvv test.mp4 2 [0x8e9357c] mp4 stream debug: found Box: ftyp size 24 3 [0x8e9357c] mp4 stream debug: found Box: free size 136 4 [0x8e9357c] mp4 stream debug: skip box: "free" 5 [0x8e9357c] mp4 stream debug: found Box: mdat size 985725 6 [0x8e9357c] mp4 stream debug: skip box: "mdat" 7 [0x8e9357c] mp4 stream debug: found Box: moov size 5187 8 [0x8e9357c] mp4 stream debug: found Box: mvhd size 108 9 [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的大小也是正确的。
(2)既然找到疑点,继续跟踪。 查看vlc的源代码,在文件modules/demux/mp4/libmp4.c中发现:skip信息是由MP4_ReadBoxSkip()函数打印的,而调用的地方在libmp4.c中2641行: 1 /* Nothing to do with this box */ 2 { FOURCC_mdat, MP4_ReadBoxSkip, MP4_FreeBox_Common }, 3 { FOURCC_skip, MP4_ReadBoxSkip, MP4_FreeBox_Common },{ FOURCC_free, MP4_ReadBoxSkip, MP4_FreeBox_Common }, 4 { FOURCC_wide, MP4_ReadBoxSkip, MP4_FreeBox_Common }, 5 6 而在libmp4.h中: 7 #define FOURCC_mdat VLC_FOURCC( 'm', 'd', 'a', 't' ) 8 #define FOURCC_skip VLC_FOURCC( 's', 'k', 'i', 'p' ) 9 #define FOURCC_free VLC_FOURCC( 'f', 'r', 'e', 'e' ) 10 #define FOURCC_wide VLC_FOURCC( 'w', 'i', 'd', 'e' ) 从代码看,vlc调用libmp4解析文件时,主动忽略了mdat,skip,free,wide这四种类型的box。 1 struct demux_sys_t 2 { 3 MP4_Box_t *p_root; /* container for the whole file */ 4 mtime_t i_pcr; 5 uint64_t i_time; /* time position of the presentation * in movie timescale */ 6 uint64_t i_timescale; /* movie time scale */ 7 uint64_t i_duration; /* movie duration */ 8 unsigned int i_tracks; /* number of tracks */ 9 mp4_track_t *track; /* array of track */ 10 float f_fps; /* number of frame per seconds */ 11 12 /* */ 13 MP4_Box_t *p_tref_chap; 14 15 /* */ 16 input_title_t *p_title; 17 }; 似乎只是为了获取mp4的tracks,moov,duration, timescale等基本信息,实际上并不解码数据,因此就不需要关注mdat这个box了。
(4)继续看vlc的输出: 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 < h->nal_length_size; i++) nalsize = (nalsize << 8) | buf[buf_index++]; 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; } … } 可以看到,正是这里报错的。
(5)在mp4v2的论坛上找到一个帖子,与我的情况非常类似:
从中得到如下关键信息: 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文件,证明了技术上是可行的。
关于该demo,可以在这里 https://github.com/ZhengfengRao/rtp2mp4 看到。 注意:很多参数都是针对我们的具体应用写死的,仅供参考。
(三)将功能合并到录制程序中。 略。 分类: UNIX &Linux |
|