迁移Cicada dash解析及嵌入demux功能

308 阅读5分钟

前一篇文章说过,为了在iOS支持dash播放,我把dash从Cicada的源码中迁移到了我们的项目里,并嵌入到demux功能,这里讲下具体的实施过程。

准备工作

  1. 首先,需要了解下Cicada的项目结构

image.png framework里面的结构

image.png 上图中红框内的是我们需要迁移的内容,其中utils里面跟android和linux相关的我们可以删掉,用不着,另外还有一些文件名是xxtest的,也需要删掉,里面有main函数,会跟我们项目中的main函数冲突。

  1. 其次,需要编译好第三方库并添加到项目中,主要就是libcurl,其他的像ffmpeg和openssl,一般做音视频的都已经有了。
  2. 配置宏, 包括ENABLE_DASH_DEMUXER,ENABLE_HLS_DEMUXER,ENABLE_CURL_SOURCE
  3. 配置 search path。 除了常规引用第三方库必须依赖的header文件夹以外,由于Cicada有一些代码直接依赖于ffmpeg的源码,因此需要在我们项目的header search path中配置ffmpeg源码的路径,也就是Cicada目录下的external/external文件夹。如果你是直接拖第三方库进来的,还需要配置下library search path

迁移文件及解决编译报错

  1. 首先把相关文件拷贝到iOS的工程里
  2. 其次是删除掉无用的代码及有冲突的代码,比如android和linux相关的代码,这个很难一一列举,只能编译时根据提示慢慢去找
  3. 修改#include ,把<>改为""。 由于原工程是把ffmpeg及相关模块都打成静态库的方式进行依赖的,所以代码里面很多文件是用尖括号的方式进行引用的,我们都需要改成双引号。
  4. 将ffmpeg中所有用到AVMediaType的地方都改个名字,比如叫FFAVMediaType。这是因为引入了C++,导致ffmpeg里面的AVMediaType跟系统的AVMediaType冲突了。
  5. 由于引入了C++,有一些引用了C的代码的地方或者本身是C文件的可能还需要增加类似下图这种的宏定义来区分C代码与C++代码,避免编译报错

image.png

接入Demuxer service

我们解析dash视频,首先是从 Demuxer service开始。Demuxer service是解析的核心。解析的大致流程是:先调用datasource下载视频,然后根据视频类型创建不同的demuxer,比如dash就是使用playlistDemuxer,一般的视频则是使用avformatDemuxer。playlistDemuxer内部会调用dashManager解析dash的mpd文件,拿到segmentTemplate后,又回调用datasource下载分段的视频,并调用dashStream解析视频流。

我接入的代码步骤如下:

  1. 创建datasource
IDataSource *dataSource = dataSourcePrototype::create(url.UTF8String);

     if  (!dataSource) {

            completion(false);

            return;

        }

        self->mDataSource = dataSource;

        ret = dataSource->Open(0);

        if (ret<0) {

            completion(false);

            return;

        }

        2. 创建并初始化demuxer service

**self**->mDemuxerService = **static_cast**<unique_ptr<demuxer_service>>(**new** demuxer_service(dataSource));

        ret = **self**->mDemuxerService->createDemuxer(demuxer_type_unknown);

        **if** (ret<0) {

            completion(**false**);

            **return**;

        }

        **self**->mDemuxerService->getDemuxerHandle()->setBitStreamFormat(header_type::header_type_extract, header_type::header_type_extract);
        ret = **self**->mDemuxerService->initOpen(demuxer_type_playlist);

        **if** (ret<0) {

            completion(**false**);

            **return**;

        }
  1. 获取流的元数据并设置缓存等
**int** nbStream = **self**->mDemuxerService->GetNbStreams();

        unique_ptr<streamMeta> pMeta;

        **int** bandWidthNearStreamIndex = -1;

        **int** minBandWidthDelta = INT_MAX;

        **int** mDefaultBandWidth = 0;

        **int** videoStreamCount = 0;

        printf("Demuxer service get nubmer streams is %d", nbStream);

        **for** (**int** i = 0; i < nbStream; ++i) {

            **self**->mDemuxerService->GetStreamMeta(pMeta, i, **false**);

            **auto** *meta = (Stream_meta *) (pMeta.get());

            **self**.duration = meta->duration;

            

            **if** (meta->type == STREAM_TYPE_MIXED || meta->type == STREAM_TYPE_VIDEO) {

                videoStreamCount++;

                **int** metaBandWidth = (**int**) meta->bandwidth;

  


                **if** (abs(mDefaultBandWidth - metaBandWidth) < minBandWidthDelta) {

                    bandWidthNearStreamIndex = i;

                    minBandWidthDelta = abs(mDefaultBandWidth - metaBandWidth);

                }

            }

        }

        **if** ( videoStreamCount > 1) {

            **if** (dataSource) {

                dataSource->enableCache(url.UTF8String, **false**);

            }

        } **else** {

            **if** (dataSource) {

                dataSource->enableCache(url.UTF8String, **true**);

            }

        }
  1. 打开流
**for** (**int** i = 0; i < nbStream; ++i) {

            **int** openStreamRet = 0;

            **self**->mDemuxerService->GetStreamMeta(pMeta, i, **false**);

            **auto** *meta = (Stream_meta *) (pMeta.get());

            **if**(meta->type == STREAM_TYPE_VIDEO){

                **if** (bandWidthNearStreamIndex == i) {

                    printf("get a video stream\n");

                    openStreamRet = **self**->mDemuxerService->OpenStream(i);

                    **self**->video_index = i;

                }

            }**else** **if** (meta->type == STREAM_TYPE_AUDIO){

                printf("get a audio stream\n");

                openStreamRet = **self**->mDemuxerService->OpenStream(i);

                **self**->audio_index = i;

            }

            

            **if** (openStreamRet < 0) {

                completion(**false**);

                **return**;

            }

        }

5.开启demuxer service

**self**->mDemuxerService->start();
  1. 开启一个while循环,从demuxer service中获取packet
        AVPacket *pkt = **nil**;
        IAFPacket *pPacket = **nullptr**;

        std::unique_ptr<IAFPacket> pMedia_Frame{};

  


        **if** (mDemuxerService == **nullptr**) {

            assert(0);

        }

  


        **int** index = -1;

        ret = mDemuxerService->readPacket(pMedia_Frame, index);

  


        **if** (pMedia_Frame == **nullptr**) {

            //  AF_LOGD("Can't read packet %d\n", ret);

            **if** (ret == 0) {

                **if** (index != -1) {

                    ret = -EAGAIN;

                }

            }

        }

        **if** (ret == 0) {

            pkt =  av_packet_alloc();

            [AVPacketUtil setNewRecordFlag:NewRecordFlagEnd forPacket:pkt];

            [**self** pushPacket:pkt];

//            [self freeContext];

            **break**;

        }**else** **if** (pMedia_Frame == **nullptr**) {

//            av_packet_free(&pkt);

            **continue**;

        }
  1. 设置packet的关键数据,如extraData,pts等
pPacket = pMedia_Frame.get();

        **if** (pPacket->getInfo().streamIndex == video_index) {

            **if** (/*mAdaptiveVideo &&*/ pPacket->getInfo().flags) {

                std::unique_ptr<streamMeta> meta;

                mDemuxerService->GetStreamMeta(meta, pPacket->getInfo().streamIndex, **false**);

                Stream_meta *pMeta = (Stream_meta*)*meta;

                pPacket->setExtraData(pMeta->extradata, pMeta->extradata_size);

                **if** (!mVideoStreamMeta) {

                    **self**->mVideoStreamMeta = (Stream_meta*)malloc(**sizeof**(Stream_meta));

                    

                    memcpy(mVideoStreamMeta,  &(*meta), **sizeof**(Stream_meta));

                    mVideoStreamMeta->extradata = (uint8_t*)malloc(pMeta->extradata_size);

                    memcpy(mVideoStreamMeta->extradata, pMeta->extradata, pMeta->extradata_size);

                }

                

            }

            

        }**else** **if** (pPacket->getInfo().streamIndex == audio_index) {

            std::unique_ptr<streamMeta> meta;

            mDemuxerService->GetStreamMeta(meta, pPacket->getInfo().streamIndex, **false**);

            Stream_meta *pMeta = (Stream_meta*)*meta;

            **if** (!mAudioStreamMeta) {

                **self**->mAudioStreamMeta = (Stream_meta*)malloc(**sizeof**(Stream_meta));

                memcpy(mAudioStreamMeta,  pMeta, **sizeof**(Stream_meta));

                mAudioStreamMeta->extradata = (uint8_t*)malloc(pMeta->extradata_size);

                memcpy(mAudioStreamMeta->extradata, pMeta->extradata, pMeta->extradata_size);

            }

    

        }

        **if** (pPacket) {

            **auto** *avAFPacket = **dynamic_cast**<AVAFPacket *>(pPacket);

            pkt = av_packet_clone(avAFPacket->ToAVPacket());

            pkt->pts = pPacket->getInfo().pts;

            pkt->dts = pPacket->getInfo().dts;

            pkt->stream_index = pPacket->getInfo().streamIndex;

        }

        **if** (!pkt) {

            **continue**;

        }
  1. 最后,就是将pkt传给解码器了,这里比较简单,就不贴代码了,

这里有几个点需要注意: 1)所有C++对象都是通过assign属性持有的,像mVideoStreamMeta这些,他们随时可能被销毁,所以使用时记得加上空判断,如果确定不用的时候,要把他置空。特别是metaInfo,本身就是从service 内部获取,不是由我们创建,所以更需要注意,如果需要长期持有,建议new一份,把需要字段copy出来。 2)我们自己创建的对象需要我们手动释放,包括demuxer service和data source,这里贴下代码,仅供参考

**if** (mDemuxerService) {

        mDemuxerService->interrupt(1);

  


        **if** (mDataSource) {

            mDataSource->Interrupt(**true**);

        }

  


   

        mDemuxerService->stop();

        mDemuxerService->close();

  


        **if** (audio_index >= 0) {

            mDemuxerService->CloseStream(audio_index);

        }

        

        **if** (video_index >= 0) {

            mDemuxerService->CloseStream(video_index);

        }

    }

  


    **if** (mDataSource) {

        mDataSource->Close();

     

        **delete** mDataSource;

        mDataSource = **nullptr**;

    }

    **if** (mVideoStreamMeta) {

        **if** (mVideoStreamMeta->extradata) {

            free(mVideoStreamMeta->extradata);

        }

        free(mVideoStreamMeta);

        mVideoStreamMeta = **nil**;

    }

    **if** (mAudioStreamMeta){

        **if** (mAudioStreamMeta->extradata) {

            free(mAudioStreamMeta->extradata);

        }

        free(mAudioStreamMeta);

        mAudioStreamMeta = **nil**;

    }

这些全部写完之后,我们的工作就差不多完成了。有不明白的地方欢迎提问。