您的位置:首页 > 编程语言

【HAS】DASH库libdash代码结构解析

2017-03-04 01:32 218 查看

libdash分析

libdash是bitmovin公司开源的一个DASH库,完整实现了DASH协议,可以在Github上获取其代码。同时,该开源库还基于QT开发了一个播放器,之前做DASH自适应算法的时候,在该实例播放器上实现过算法,也对该播放器的代码结构较为熟悉,特记录下来,本文将主要针对设计自适应算法部分的代码。

整体结构



每一个类的作用

1. DASHPlayer

class DASHPlayer : public IDASHPlayerGuiObserver, public managers::IMultimediaManagerObserver


这个sample player用的最多的一种设计模式就是观察者模式,可以看到几乎下面每一个类都是继承了几个观察者类,通过观察者这种形式,来获取其包含的类的状态信息,包括下载状态、buffer状态以及播放器按钮等。DASHPlayer继承了IDASHPlayerGuiObserver和IMultimediaManagerObserver,其将观察DASHPlayerGui和MultimediaManager两个类的状态,分别对应着下面两组函数。同时,DASHPlayer主要的两个包含的类就是QtSamplePlayerGui和MultimediaManager,前者负责QT界面的功能,后者负责媒体的下载和播放。

virtual void OnSettingsChanged      (int period, int videoAdaptationSet, int videoRepresentation, int audioAdaptationSet, int audioRepresentation);
virtual void OnStartButtonPressed   (int period, int videoAdaptationSet, int videoRepresentation, int audioAdaptationSet, int audioRepresentation);
virtual void OnStopButtonPressed    ();
virtual void OnDownloadMPDPressed   (const std::string &url);


当QT界面的DownloadMPD按钮按下的话,将执行该函数里的内容,其余的类似,分别是Start, Stop, Setting所对应的功能。下面简单介绍一下,OnDownloadMPDPressed函数的功能,

void DASHPlayer::OnDownloadMPDPressed               (const std::string &url)
{
if(!this->multimediaManager->Init(url))
{
this->gui->SetStatusBar("Error parsing mpd at: " + url);
return; // TODO dialog or symbol that indicates that error
}

this->SetSettings(-1, -1, -1, -1, -1);
this->gui->SetStatusBar("Successfully parsed MPD at: " + url);
this->gui->SetGuiFields(this->multimediaManager->GetMPD());
}


首先,会初始化multimediaManager中的相关内容,其中就包括mpd的下载等,另外,会对QT界面gui进行一些设置,改变按钮的状态之类的。

/* IMultimediaManagerObserver */
virtual void OnVideoBufferStateChanged          (uint32_t fillstateInPercent);
virtual void OnVideoSegmentBufferStateChanged   (uint32_t fillstateInPercent);
virtual void OnAudioBufferStateChanged          (uint32_t fillstateInPercent);
virtual void OnAudioSegmentBufferStateChanged   (uint32_t fillstateInPercent);


这几个函数,负责监视MultimediaManager的状态,相应的状态发生改变时,会执行其中的函数,主要是设计到QT界面上关于buffer部分的显示等。

bool    SettingsChanged (int period, int videoAdaptationSet, int videoRepresentation, int audioAdaptationSet, int audioRepresentation);
void    SetSettings     (int period, int videoAdaptationSet, int videoRepresentation, int audioAdaptationSet, int audioRepresentation);


DASHPlayer也可以更改将要下载的视频的质量,主要是上面两个函数来实现的,其主要是为了实现通过QT界面来调节的功能,但是,自适应算法最好不要在这里实现,太顶层了,而且,整个代码结构一直都是通过观察者传递,传到这一层会经过很多的类,写起来很麻烦。

2. MultimediaManager

MultimediaManager
的功能比较强大,负责三个方面:

一,负责下载音视频片段,主要的类是
MultimediaStream
,针对每一个视频流或者音频流都会创建一个
MultimediaStream
,所以,具体自适应逻辑应该作用在在
MultimediaStream
中。这一部分的介绍,将放在下一章节里讲述。

二,负责音视频的渲染,有两个创建音频和视频渲染线程的函数如下,其主要是调用了ffmpeg的类库,

/* Threads for Rendering Audio & Video */
bool            StartVideoRenderingThread   ();
void            StopVideoRenderingThread    ();
static void*    RenderVideo                 (void *data);

bool            StartAudioRenderingThread   ();
void            StopAudioRenderingThread    ();
static void*    RenderAudio                 (void *data);


可以看一下,RenderVideo这个函数的内容,

void*   MultimediaManager::RenderVideo        (void *data)
{
MultimediaManager *manager = (MultimediaManager*) data;

QImage *frame = manager->videoStream->GetFrame();

while(manager->isVideoRendering)
{
if (frame)
{
manager->videoElement->SetImage(frame);
manager->videoElement->update();

manager->framesDisplayed++;

PortableSleep(1 / manager->frameRate);

delete(frame);
}

frame = manager->videoStream->GetFrame();
}

return NULL;
}


此处一定要分清manager是 MultimediaManager本身,而不是DASHManger,可以参考第一行代码,
MultimediaManager *manager = (MultimediaManager*) data;


可以看到,manager存在两个成员变量,
manager->videoElement->update();
, videoElement负责更新每一帧图像,
manager->videoStream->GetFrame();
, videoStream负责获取下一帧图像。

三、负责自适应逻辑的设置和调整,具体相关的函数如下,

void    MultimediaManager::InitVideoRendering               (uint32_t offset)
{
this->videoLogic = AdaptationLogicFactory::Create(libdash::framework::adaptation::Manual, this->mpd, this->period, this->videoAdaptationSet);

this->videoStream = new MultimediaStream(sampleplayer::managers::VIDEO, this->mpd, SEGMENTBUFFER_SIZE, 2, 0);
this->videoStream->AttachStreamObserver(this);
this->videoStream->SetRepresentation(this->period, this->videoAdaptationSet, this->videoRepresentation);
this->videoStream->SetPosition(offset);
}


在初始化的时候,就设置了自适应逻辑,只要修改
libdash::framework::adaptation::Manual
的值,即可通过简单工厂模式,选择不同的自适应逻辑。目前库里自带的逻辑都是最简单的实例性质的。

如果在运行中修改自适应逻辑的话,可以根据下面两个函数,当然现在都没有具体的实现,可以参考上边的创建来实例化一个自适应逻辑并赋值给
videoLogic
即可。

bool SetVideoAdaptationLogic    (libdash::framework::adaptation::LogicType type);
bool SetAudioAdaptationLogic    (libdash::framework::adaptation::LogicType type);


3. MultimediaStream

该类负责一路音频或者视频流,主要的功能有两个方面,

一、负责控制下载或者暂停,主要函数如下,具体就是控制
DASHManger
来实现,

bool        MultimediaStream::StartDownload             ()
{
if(!dashManager->Start())
return false;

return true;
}
void        MultimediaStream::StopDownload              ()
{
this->dashManager->Stop();
}


二、存储解码好的视频帧,并负责交给上层
MultimediaManger
渲染,涉及到的代码如下,

libdash::framework::buffer::Buffer<QImage>          *frameBuffer;
libdash::framework::buffer::Buffer<libdash::framework::buffer::AudioChunk> *sampleBuffer;

void        AddFrame                (QImage *frame);
QImage*     GetFrame                ();


4. DASHManger

DASHManger
具体控制下载和解码,主要的成员有,

buffer::MediaObjectBuffer   *buffer;
MediaObjectDecoder          *mediaObjectDecoder;
DASHReceiver                *receiver;


其中
DASHReceiver
获取到的MediaObject会被放在
buffer
中,
MediaObjectDecoder
负责解码
buffer
中的数据,并放到上边那个类的frameBuffer中。

5. DASHReceiver

这个类,涉及到下载的最顶层的内容,主要看
doBuffering
这个线程,里边涉及到每一个块的下载,故而,可以在此处计算下载每一个块所用的时间,然后,就可以估计网络状态,是个很重要的变量,

/* Thread that does the buffering of segments */
void*                       DASHReceiver::DoBuffering               (void *receiver)
{
DASHReceiver *dashReceiver = (DASHReceiver *) receiver;

dashReceiver->DownloadInitSegment(dashReceiver->GetRepresentation());

MediaObject *media = dashReceiver->GetNextSegment();

while(media != NULL && dashReceiver->isBuffering)
{
media->StartDownload();

if (!dashReceiver->buffer->PushBack(media))
return NULL;

media->WaitFinished();

dashReceiver->NotifySegmentDownloaded();

media = dashReceiver->GetNextSegment();
}

dashReceiver->buffer->SetEOS(true);
return NULL;
}


到此处,涉及到自适应逻辑的每一个类都讲过了,那么,自适应逻辑该加在什么地方呢?其实可以加的地方还是蛮多的,而且,对性能影响也不大,如果设计的是,每下载完一个segment做一次逻辑的话,可以放在
OnSegmentDownloaded
这个函数里,我是将逻辑放在
MultimediaManger
中的,因为涉及到音频流和视频流,要做一次流判断,可以在
MultimediaManger
OnSegmentDownloaded
中如下添加代码,

void    MultimediaManager::OnSegmentDownloaded              (StreamType type, double current_bandwidth)
{
this->segmentsDownloaded++;

estimate_bandwidth.push_back(current_bandwidth);

switch (type)
{
case AUDIO:
this->audioLogic->EstimateBandwidth(estimate_bandwidth);
this->audioLogic->DoLogic();
this->SetAudioQuality(this->period, this->audioLogic->GetAdaptationSet(), this->audioLogic->GetRepresentation());
break;
case VIDEO:
this->videoLogic->EstimateBandwidth(estimate_bandwidth);
this->videoLogic->DoLogic();
this->SetVideoQuality(this->period, this->videoLogic->GetAdaptationSet(), this->videoLogic->GetRepresentation());
break;
default:
break;
}
}


我在自己的Github仓库里,大概把该有的内容都添加进来了,只需要在逻辑类做相应的实现即可,现在逻辑类可以获取的参数有,buffer的状态以及预测带宽,这两块的具体实现以及自适应算法的实现都很有意义,欢迎大家一起来研究讨论这些问题。

更多内容可以关注我的个人博客地址:【HAS】DASH库libdash代码结构解析
内容来自用户分享和网络整理,不保证内容的准确性,如有侵权内容,可联系管理员处理 点击这里给我发消息
标签:  HAS DASH