1. 程式人生 > >基於EasyDarwin 流媒體播放器的客戶端開發

基於EasyDarwin 流媒體播放器的客戶端開發

 最近接觸了一個國內優秀的流媒體平臺- EasyDarwin。這是一個國外開源流媒體平臺Darwin深度裁剪版本,看了一段時間後就想跟自己的開發的一個android裝置對接,以了卻我多年對多媒體更深入的理解。(本人曾經自己開發一個H264的移動電視的軟解碼播放器)

         在搭建好伺服器後,拿出了全志的T2開發板,開始使用其團隊的EasyPusher庫進行推送。首先我嘗試視訊直播的開發,在研究了兩天後,發現為我的機器的系統的解碼器已經被封裝成庫,對外不開放。所以我不得不使用其android系統的mediaRecoder進行編碼,而系統已經把其直接打包成了MP4檔案,這樣我無法獲得直接的H264資料。於是陷入了困頓之中。在等待方案公司的幫助的時候,我尋找了別的方法去獲得推送的資料流,最後不得不採用localhost技術,該技術就是將mediaRecoder編碼的資料通過類似本地網路管道

的方式接受下來。在接受到資料後發現還是無法進行傳送。因為接受到資料沒有任何MP4相關的描述資訊只有資料流。一個MP4檔案的資料能夠播放,是因為在MP4檔案中有很多的BOX構成,其中尤其有兩個box非常重要:moovboxmdatbox其中前者包含了MP4內的巨集觀的描述資訊,後者包含的實際的資料。因為mediarecoder在處理資料後才將其中的moovbox的資料一次性寫進MP4檔案,所以我能獲得只有mdatbox的資料。沒有辦法只好對從根據輸入引數來獲得相關資訊以及大致估計時間戳。具體做法如下:

第一步:搜尋MDAT資料頭

第二步:將其中的資料解析成H264的資料包,其中資料幀的長度剛好四個位元組,用

H264的頭(0x00,0x00,0x00,0x01來代替。)

第三步:呼叫EasyPusher將資料傳送到伺服器。這裡需要注意的是,遇到I幀的時候需要加上SPSPPS。當然localhost的資料是不會有這些資料的,只好自己根據解析度等資訊寫好。如下

const unsigned char SPSPPSCIF[] ={0X00,0X00,0X00,0X01, 0x67,0x42, 0X00, 0X1F, 0XE5, 0X40, 0XA0, 0xfc,0X80,0X00,0X00,0X00,0X01,0X68,0XCE,0x31,0X12};//320*240

const unsigned char SPSPPS576[] ={0X00,0X00,0X00,0X01, 0x67,0x42, 0X00, 0X1F, 0XE5, 0X40, 0X5A, 0x09,0X32,0X00,0X00,0X00,0X01,0X68,0XCE,0x31,0X12};     //720*576

這些資料最好出現I幀(幀頭第5個位元組一般是0X67,參照H264資料規範)加上。我估計這對流媒體傳輸來說是很重要的。

傳輸過去後,在客戶端可以看到視訊,但丟幀非常嚴重。我估計主要是因為localhost以及編碼器效率太低,同時時間戳也是一個估計數。所以造成了丟幀現象。沒有什麼好的辦法了,只有等待硬體編碼器的相關資料直接編碼進行傳送了。

繞過直播,開始搞回放。首先選擇一個DEMUX工具,我選擇的是MP4V2,主要是這個工具WINDOWS和android NDK版本都有,所以相對來說比較省時間。折騰一段時間後發現數據能傳輸上去了。但是音訊和視訊完全不同步。在跟EasyDarwin 團隊的一個朋友討論後我決定開始搞音視訊同步,策略是採用的是講音視訊幀都同步到系統時間。果然效果還是很明顯的,除了偶爾會卡住基本上能流暢播放。我估計著需要開始瞭解播放器端的同步了。相關的程式碼附錄上:

MP4 DEMUX:獲取視訊和音訊幀:

typedef struct _Media_INFO_

{

         MP4FileHandle  mp4File;

         MP4TrackId     video_trId;

         MP4TrackId     audio_trId;

   u_int32_t     video_sample_max_size;

         u_int32_t      video_num_samples;

   u_int32_t      video_width;

         u_int32_t            video_height;

   double         video_frame_rate;

   u_int32_t      video_timescale;

         u_int32_t      audio_num_samples;

   int            audio_num_channels;

   u_int32_t      audio_timescale;

   MP4Duration    audio_duration;

         u_int32_t                   avgBitRate;

         u_int8_t            AudioType;

   u_int8_t       *p_audio_config;

   u_int32_t     n_audio_config_size;

         u_int32_t      n_video_config_size;

   u_int32_t     audio_sample_max_size;

         MP4Duration    video_duration;

         u_int8_t     *p_Vediobuffer;

         u_int8_t     *p_Audio_buffer;

}MEDIA_INFO;

 staticint GetVideoStreamHeader(MP4FileHandle mp4File, MP4TrackId video_trId, intvideo_codec,

                                unsigned char*strm_hdr_buf, int *strm_hdr_leng)

{

   int            b;

   // for MPEG4

   unsigned char      *p_video_conf;

   unsigned int      n_video_conf;

   // for H.264

   unsigned char     **pp_sps,**pp_pps;

    unsigned int     *pn_sps, *pn_pps;

   unsigned int       n_strm_size;

   int            i;

   switch (video_codec) {

   case RAW_STRM_TYPE_M4V:        //MPEG4

       p_video_conf = NULL;

       n_video_conf = 0;

       b = MP4GetTrackESConfiguration(mp4File, video_trId,

                                      &p_video_conf, &n_video_conf);

       if (!b)

           return -1;

       memcpy(strm_hdr_buf, p_video_conf, n_video_conf);

       free(p_video_conf);

       *strm_hdr_leng = n_video_conf;

       break;

   case RAW_STRM_TYPE_H263:        //H.263

       *strm_hdr_leng = 0;

       break;

   case RAW_STRM_TYPE_H264RAW:       // H.264

       pp_sps = pp_pps = NULL;

       pn_sps = pn_pps = NULL;

       n_strm_size     = 0;

       b = MP4GetTrackH264SeqPictHeaders(mp4File, video_trId, &pp_sps,&pn_sps, &pp_pps, &pn_pps);

       if (!b)

           return -1;

       // SPS memcpy

       if (pp_sps) {

           for (i=0; *(pp_sps + i); i++) {

                memcpy(strm_hdr_buf +n_strm_size, h264_delimiter, sizeof(h264_delimiter));

                n_strm_size +=sizeof(h264_delimiter);

                memcpy(strm_hdr_buf +n_strm_size, *(pp_sps + i), *(pn_sps + i));

/*

                if(NAL_UNIT_TYPE_TYPE(strm_hdr_buf[n_strm_size]) == 7) {

                    strm_hdr_buf[n_strm_size +1] = 66;

                }

*/

                n_strm_size += *(pn_sps + i);

                free(*(pp_sps + i));

           }

           free(pp_sps);

       }

       // PPS memcpy

       if (pp_pps) {

           for (i=0; *(pp_pps + i); i++) {

                memcpy(strm_hdr_buf +n_strm_size, h264_delimiter, sizeof(h264_delimiter));

                n_strm_size +=sizeof(h264_delimiter);

                memcpy(strm_hdr_buf + n_strm_size,*(pp_pps + i), *(pn_pps + i));

                n_strm_size += *(pn_pps + i);

                free(*(pp_pps + i));

           }

           free(pp_pps);

       }

       *strm_hdr_leng = n_strm_size;

       break;

   default:    // Unknown

        *strm_hdr_leng = 0;

       break;

    }

   return 0;

}

int GetAudioFrame(MEDIA_INFO* pMedia,unsigned int *pSize,   intsample_id,  unsigned long* timebase ,unsigned long* dutation)

{

   int nRead;

   u_int8_t      *p_audio_sample;

   u_int32_t      n_audio_sample;

   int            b, ret;

         MP4Timestamp   TimeStamp;

   MP4Duration    Audio_duration;

   p_audio_sample = (u_int8_t *) pMedia->p_Audio_buffer;;

         *pSize= pMedia->audio_sample_max_size;

   n_audio_sample = *pSize;

   if ((sample_id <= 0) || (sample_id >pMedia->audio_num_samples)) {

       *pSize = 0;

       return -1;

    }

   /////////////////////

   //  MP4ReadSample  //

   /////////////////////

         b= MP4ReadSample(pMedia->mp4File,pMedia->audio_trId, sample_id,

                      &p_audio_sample,&n_audio_sample,

                      &TimeStamp,&Audio_duration, NULL, NULL);

   if (!b) {

       *pSize = 0;

       return -1;

    }

   *pSize = nRead = n_audio_sample;

         *timebase= TimeStamp;

         *dutation= Audio_duration;

   return nRead;

}

int GetVideoFrame(MEDIA_INFO* pMedia, intm_video_codec,  unsigned int *pSize,unsigned int *isIframe,  int sample_id,unsigned long* timebase, unsigned long*duration )

{

   int nRead;

         int            n_video_hdr;

         u_int8_t      *p_video_sample;

   u_int32_t      n_video_sample;

         MP4Timestamp   TimeStamp;

   MP4Duration    video_duration;

   int            b, ret;

   p_video_sample = (u_int8_t *) pMedia->p_Vediobuffer;

         *pSize=  pMedia->video_sample_max_size;

   n_video_sample = *pSize;

         if((sample_id <= 0) || (sample_id > pMedia->video_num_samples)) {

       *pSize = 0;

       return -1;

    }

         *isIframe  = MP4GetSampleSync(pMedia->mp4File,pMedia->video_trId, sample_id);

          if (*isIframe ) {

       ret = GetVideoStreamHeader(pMedia->mp4File, pMedia->video_trId,m_video_codec,

                                  p_video_sample, &n_video_hdr);

       p_video_sample += n_video_hdr;

    }

   else

       n_video_hdr = 0;

   /////////////////////

   //  MP4ReadSample  //

   /////////////////////

    b= MP4ReadSample(pMedia->mp4File, pMedia->video_trId, sample_id,

                      &p_video_sample,&n_video_sample,

                      &TimeStamp,&video_duration, NULL, NULL);

   if (!b) {

       *pSize = 0;

       return -1;

    }

         //if (codec == h.264), the first 4 bytes are the length octets.

   // They need to be changed to H.264 delimiter (00 00 00 01).

   if (m_video_codec == RAW_STRM_TYPE_H264RAW) {

       int h264_nal_leng;

        int nal_leng_acc = 0;

       do {

           h264_nal_leng = p_video_sample[0];

           h264_nal_leng = (h264_nal_leng << 8) | p_video_sample[1];

           h264_nal_leng = (h264_nal_leng << 8) | p_video_sample[2];

           h264_nal_leng = (h264_nal_leng << 8) | p_video_sample[3];

           memcpy(p_video_sample, h264_delimiter, 4);

           nal_leng_acc   += (h264_nal_leng +4);

           p_video_sample += (h264_nal_leng + 4);

       } while (nal_leng_acc < n_video_sample);

    }

         *timebase= TimeStamp;//TimeStamp+video_duration;

         *duration= video_duration;

   *pSize = nRead = (n_video_hdr + n_video_sample);

       return nRead;

}

時鐘同步:

/返回需要延遲的時鐘

typedef struct _SYN_CLOCK_CTRL_

{

         unsignedchar Vedioflag;

         unsignedchar Audioflag;

         unsignedlong ClockBase;

         unsignedlong ClockCurr;

         unsignedlong VedioBase;

         unsignedlong AudioBase;

}Sync_clock_Ctl;

Sync_clock_Ctl g_clock;

unsigned long Sync_clock(MEDIA_INFO*pMedia, unsigned long timebase, unsigned long duration, int type, unsignedlong* OutTime)

{

         unsignedlong TimebaseNew;

         unsignedlong DiffClock;

         doubleTimeCalbase;

         doubleTimenext;

         unsignedlong CurrentTime;

         unsignedlong NextTime;

         unsignedlong delay;

         unsignedlong TimeScale;

#ifdef _WIN32

         if(g_clock.ClockBase== 0)

         {

                   g_clock.ClockBase= ::GetTickCount();

         }

         g_clock.ClockCurr= ::GetTickCount();

#else

         {

                   structtimeval tv;

                   gettimeofday(&tv,NULL);

                   //return(int64_t)tv.tv_sec * 1000000 + tv.tv_usec;

                   g_clock.ClockCurr= (int64_t)tv.tv_sec * 1000 + tv.tv_usec/1000;

                   if(g_clock.ClockBase== 0)

                   {

                            g_clock.ClockBase= g_clock.ClockCurr;

                   }

         }

#endif

         DiffClock= g_clock.ClockCurr - g_clock.ClockBase;//時鐘的耗時間Tick數//微妙級別忽略不計

         if(type== VEDIO_PUSH)

         {

                   if(g_clock.Vedioflag== 0)

                   {

                            g_clock.VedioBase= timebase;

                            g_clock.Vedioflag= 1;

                   }

                   TimeScale= pMedia->video_timescale;

         }else

         {

                   if(g_clock.Audioflag== 0)

                   {

                            g_clock.AudioBase= timebase;

                            g_clock.Audioflag= 1;

                   }

                   TimeScale= pMedia->audio_timescale;

         }

         TimeCalbase= ((double)(timebase-g_clock.VedioBase))/TimeScale;

         Timenext= ((double)(timebase-g_clock.VedioBase+duration))/TimeScale;

         //開始計算當前和小一個Sample的時間估計決定延遲//

         CurrentTime= (unsigned long)(TimeCalbase*1000);

         NextTime= (unsigned long)(Timenext*1000);

         *OutTime= CurrentTime;

         if(DiffClock> NextTime) //已經落後,快進

         {

                   delay=  0;

         }else

         {

                   delay= NextTime- DiffClock;//重新計算時間

         }

         returndelay;

}