OpenHarmony南向开发:深入浅出了解Wi-Fi Display

30 篇文章 0 订阅
5 篇文章 0 订阅

往期鸿蒙全套实战精彩文章必看内容:


Wi-Fi Display是什么?

Wi-Fi Display(缩写为WFD)经常和Miracast联系在一起。实际上,Miracast是Wi-Fi联盟(Wi-Fi Alliance)对支持WFD功能的设备的认证F名称。通过Miracast认证的设备将在最大程度内保持对WFD功能的支持和兼容。所以WFD是一个Miracast的规范。

WFD涉及的协议和技术

图片

从图中可以发现,WFD主要涉及以下几个技术:

1.音视频编解码

2.视频格式muxer和demuxer

3.RTP封包与解包

4.TCP,UDP网络

5.Wi-Fi p2p

6.UIBC(用户反向控制)

至于音视频采集和渲染是否带内,需要看具体实现。

首先WFD实际就是一个基于Wi-Fi p2p和C/S架构的流媒体程序。

服务端是source(投屏端),客户端是sink(被投端)

WFD工作原理

下面是一个WFD的简单工作流程:

source端:

设备发现--->选择sink设备--->建立连接--->协商媒体信息 --->传输音视频数据

sink端:

等待设备发现 ---> 处理源设备的连接 --->协商媒体信息--->接收音视频数据

WFD开源实现(基于castengine_WFD)

source端由于篇幅过长,就不介绍了,感兴趣的可以自己去研究源码,source主要流程:

1.初始化并使能p2p

2.发现p2p设备

3.连接sink设备后,会建立rtsp server(tcp监听端口7236),创建音视频传输的udp等。

4.source端采集手机画面然后进行编解码后通过rtp传输到sink。

接下来主要讲解下sink端流程:

目前我们已经大概了解了WFD工作原理了,作为sink端肯定是需要等待source端设备发现,但是这个前提是需要先开启p2p,如何开启呢?

castengine_Wi-Fi_display/tests/demo/wfd/wfd_demo.cpp

void WfdDemo::RunWfdSink()
{
    if (!Init(SINK))
        return;

    if (client_->Start() == 0) {
        std::cout << "wifi display sink service start!\n";
    }
    ...
}

这里Init函数主要是创建一个wfd sink,并且设置一些连接相关的回调函数。

client_->Start()函数里就是使能Wi-Fi p2p了,这样source端才能发现设备。

castengine_wifi_display\services\impl\scene\wfd\wfd_sink_scene.cpp

int32_t WfdSinkScene::HandleStart(std::shared_ptr<WfdSinkStartReq> &msg, std::shared_ptr<WfdCommonRsp> &reply)
{
    ...
    p2pInstance_->GetP2pEnableStatus(status);
    switch (status) {
        ...
        case (int32_t)Wifi::P2pState::P2P_STATE_STARTED:
            isSinkRunning_ = true;
            WfdP2pStart();
            break;
        case (int32_t)Wifi::P2pState::P2P_STATE_CLOSED:
            isSinkRunning_ = true;
            p2pInstance_->EnableP2p();
            break;
        default:
            SHARING_LOGI("none process case.");
            break;
    }

    return ret;
}

最终上面的Start会调用到这里,这里首先先获取p2p当前状态,然后会开启p2p。

如果此时source端已经选择了sink设备,这时候p2p连接建立后sink端怎么知道呢?

这个就要回到刚才的Init函数了,Init里会设置listener,然后通过回调获取状态。

castengine_wifi_display\tests\demo\wfd\wfd_demo.cpp

void WfdDemo::OnDeviceState(const CastDeviceInfo &deviceInfo)
{
    switch (deviceInfo.state) {
        case CastDeviceState::CONNECTED: {
            {
             ...
client_->AppendSurface(deviceInfo.deviceId, surfaceIds_[item]);
client_->SetMediaFormat(deviceInfo.deviceId,videoAttr_,audioAttr_);
client_->Play(deviceInfo.deviceId);
            ...
        case CastDeviceState::DISCONNECTED: {
            ...
        }
        default:
            break;
    }
}

可以看到这里处理了miracast设备的连接与断开。

如果此时miracast设备连接了,接下来该做什么呢?

显然,从上图中的demo里可以看到接下来就是播放投屏端的音视频数据了。由于demo为了简单,所以这里直接设置了媒体格式,而不是协商,但是这并不影响我们分析流程。接下来看sink端如何播放显示音视频数据。

1. 调用AppendSurface

2. 调用Play

AppendSurface函数主要工作:

1. 创建了视频解码器

2. 创建了视频播放线程

3. 等待用户播放

Play函数主要工作:

1. 根据需要创建音频解码器

2. 创建音频播放线程

3. 设置播放状态为正在播放

AppendSurface具体工作流程如下:

从用户APP调用AppendSurface开始,通过IPC调用,最终这个函数会调用到下面

castengine_wifi_display\services\mediaplayer\src\media_controller.cpp

bool MediaController::AppendSurface(sptr<Surface> surface, SceneType sceneType)
{
       ...
        if (isPlaying_) {
            auto mediaChannel = mediaChannel_.lock();
            auto dispatcher = mediaChannel->GetDispatcher();
            videoPlayController->Start(dispatcher);
        }
        ...
    return true;
}

videoPlayController->Start(dispatcher)实现如下

castengine_wifi_display\services\mediaplayer\src\video_play_controller.cpp

bool VideoPlayController::Start(BufferDispatcher::Ptr &dispatcher)
{
    ...
      if (videoSinkDecoder_->Start()) {
            ...
            StartVideoThread();
            return true;
        }
   ...
    return false;
}

可以看到播放线程主要做两件事:

1. 读取投屏端发过来的视频数据

2. 处理视频数据

void VideoPlayController::ProcessVideoData(const char *data, int32_t size)
{
    ...
    videoSinkDecoder_->DecodeVideoData(data, size, bufferIndex);
}

这里解码视频数据。怎么把数据送给解码器解码呢?

castengine_wifi_display\services\codec\src\video_sink_decoder.cpp

bool VideoSinkDecoder::DecodeVideoData(const char *data, int32_t size, const int32_t bufferIndex)
{
   ...
    auto inputBuffer = videoDecoder_->GetInputBuffer(bufferIndex);
    ...
    auto ret = memcpy_s(inputBuffer->GetBase(),inputBuffer->GetSize(), data, size);
    ...
    Media::AVCodecBufferInfo bufferInfo;
    bufferInfo.presentationTimeUs = 0;
    bufferInfo.size = size;
    bufferInfo.offset = 0;

    auto p = data;
    p = *(p + 2) == 0x01 ? p + 3 : p + 4; // 2: offset, 3: offset, 4: offset
    if ((p[0] & 0x1f) == 0x06 || (p[0] & 0x1f) == 0x07 || (p[0] & 0x1f) == 0x08) {
        ret = videoDecoder_->QueueInputBuffer(bufferIndex, bufferInfo, Media::AVCODEC_BUFFER_FLAG_CODEC_DATA);
    } else {
        ret = videoDecoder_->QueueInputBuffer(bufferIndex, bufferInfo, Media::AVCODEC_BUFFER_FLAG_NONE);
    }
    ...
    return true;
}

可以看到先是拿到一块解码器的可用输入buffer,然后送去解码。

那么解码后的数据从哪里拿到呢?

其实是通过回调来拿到解码后数据

void VideoSinkDecoder::OnOutputBufferAvailable(uint32_t index, Media::AVCodecBufferInfo info,Media::AVCodecBufferFlag flag)
{
    ...
    listerner->OnVideoDataDecoded(dataBuf);
    ...
}

当解码器初始化的时候会设置一些回调用。具体可以看代码。

最后这里会调用OnVideoDataDecoded,把解码后的数据拿去渲染。

castengine_wifi_display\services\mediaplayer\src\video_play_controller.cpp

void VideoPlayController::OnVideoDataDecoded(DataBuffer::Ptr decodedData)
{
   ...
   RenderInCopyMode(decodedData);
}
int32_t VideoPlayController::RenderInCopyMode(DataBuffer::Ptr decodedData)
{
    ...
    SurfaceError error = surface_->RequestBuffer(buffer, releaseFence, requestConfig);
      ...
    surface_->FlushBuffer(buffer, -1, flushConfig);
    return 0;
}

这里OnVideoDataDecoded回调拿到数据后就开始渲染视频数据到surface上。

Play具体工作流程如下:

从用户APP调用Play开始,通过IPC调用,最终这个函数会调用到下面

void MediaController::Start()
{
    ...
     RETURN_IF_NULL(dispatcher);
    {
        std::lock_guard<std::mutex> lock(playAudioMutex_);
        if (nullptr != audioPlayController_) {
            if (audioPlayController_->Start(dispatcher)) {
                isPlaying_ = true;
            }
        }
    }
    ...
}

可以看到最主要的工作就是:

1. 开启音频播放线程

2. 让音视频播放线程工作,通过置为isPlaying_到true控制。

总结

以上就是WFD的sink端大概工作流程,其实就是一个Wi-Fi p2p和一个视频播放器的结合。

看完三件事❤️

  • 如果你觉得这篇内容对你还蛮有帮助,我想邀请你帮我三个小忙:
  • 点赞,转发,有你们的 『点赞和评论』,才是我创造的动力。
  • 关注作者 ,不定期分享原创知识。
  • 同时可以期待后续文章ing🚀。   
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值