EasyDarwin开源流媒体云平台之语音对讲功能设计与实现

本文由EasyDarwin开源团队成员Alex贡献:http://blog.csdn.net/cai6811376/article/details/52006958

EasyDarwin云平台一直在稳步的升级迭代中,近日,EasyDarwin云平台实现了语音对讲的功能。对讲功能的加入,标志着EasyDarwin云平台进一步的完善。

  • 流程设计

    1. 客户端使用POST的方式在body中携带协议报文向云平台发送开始对讲命令;

    2. 云平台组织协议报文向指定的设备发送;

    3. 设备执行开始对讲命令后向云平台返回相应报文;

    4. 云平台将响应报文返回给客户端;

    5. 客户端请求开始对讲成功后,开始向云平台发送采集的音频数据;

    6. 云平台将音频数据转发给设备;

    7. 设备将音频数据播放出来;

    8. 客户端需要停止对讲时,向云平台发送停止对讲命令;

    9. 云平台转发给设备;

    10. 设备执行停止对讲后返回执行结果给云平台;

    11. 云平台将结果返回给客户端。

tb

  • 协议设计
//客户端向云平台发送对讲报文
{
   "EasyDarwin": {
      "Body": {
         "Channel": "0",
         "Command": "START",
         "AudioType": "G711A",
         "Protocol": "ONVIF",
         "Reserve": "1",
         "Serial": "001001000058",
         "Preset": "3",
         "AudioData": "BASE64DATA",
         "Pts": "20"
      },
      "Header": {
         "CSeq": "1",
         "MessageType": "MSG_CS_TALKBACK_CONTROL_REQ",
         "Version": "1.0"
      }
   }
}

格式说明:
Serial:设备序列号;
Channel:摄像机通道号;
Protocol:指定预置位控制方式,ONVIF协议或者设备SDK;
Command:对讲控制命令,包括启动对讲、停止对讲、发送对讲数据(START/STOP/SENDDATA);
AudioType:音频数据类型,包括G711A/G726;
AudioData:音频数据,将音频数据通过Base64编码发送;
Pts:音频时间戳,开始为0,每20ms增加20;
Reserve:预留。

//云平台响应客户端请求
{
    "EasyDarwin": {
        "Body": {
            "Channel": "0",
            "Protocol": "ONVIF",
            "Reserve": "1",
            "Serial": "001001000058"
        },
        "Header": {
            "CSeq": "1",
            "ErrorNum": "200",
            "ErrorString": "Success OK",
            "MessageType": "MSG_SC_TALKBACK_CONTROL_ACK",
            "Version": "1.0"
        }
    }
}
//云平台向设备发送对讲命令
{
   "EasyDarwin": {
      "Body": {
         "Channel": "0",
         "Command": "SENDDATA",
         "AudioType": "G711A",
         "AudioData": "BASE64DATA",
         "Pts": "20",
         "From": "f6a221eec46b47dea8ae1a2bd11f8d02",
         "Protocol": "ONVIF",
         "Reserve": "1",
         "Serial": "001001000058",
         "To": "245d6ec33cd247b7b7524219552db4d8",
         "Via": "27823d2e8b6b4032b453d435a16b7be8"
      },
      "Header": {
         "CSeq": "1",
         "MessageType": "MSG_SD_CONTROL_TALKBACK_REQ",
         "Version": "1.0"
      }
   }
}

格式说明:
From: EasyCMS接收Client访问的SessionID;
To: EasyCMS向Device发送报文的SessionID;
Via: EasyCMS的ServiceID;

//设备向云平台响应对讲命令
{
   "EasyDarwin": {
      "Body": {
         "Channel": "0",
         "From": "245d6ec33cd247b7b7524219552db4d8",
         "Protocol": "ONVIF",
         "Reserve": "1",
         "Serial": "001001000058",
         "To": "f6a221eec46b47dea8ae1a2bd11f8d02",
         "Via": "27823d2e8b6b4032b453d435a16b7be8"
      },
      "Header": {
         "CSeq": "1",
         "ErrorNum": "200",
         "ErrorString": "Success OK",
         "MessageType": "MSG_DS_CONTROL_TALKBACK_ACK",
         "Version": "1.0"
      }
   }
}
  • 实现
//EasyCMS HTTPSession.cpp execNetMsgCSTalkbackControlReq
QTSS_Error HTTPSession::execNetMsgCSTalkbackControlReq(const char* json)
{
    if (!fAuthenticated)//没有进行认证请求
        return httpUnAuthorized;

    EasyProtocol req(json);

    string strDeviceSerial = req.GetBodyValue(EASY_TAG_SERIAL);
    string strChannel = req.GetBodyValue(EASY_TAG_CHANNEL);
    string strProtocol = req.GetBodyValue(EASY_TAG_PROTOCOL);
    string strReserve = req.GetBodyValue(EASY_TAG_RESERVE);
    string strCmd = req.GetBodyValue(EASY_TAG_CMD);
    string strAudioType = req.GetBodyValue(EASY_TAG_AUDIO_TYPE);
    string strAudioData = req.GetBodyValue(EASY_TAG_AUDIO_DATA);
    string strPts = req.GetBodyValue(EASY_TAG_PTS);

    string strCSeq = req.GetHeaderValue(EASY_TAG_CSEQ);//这个是关键字

    if (strChannel.empty())
        strChannel = "0";
    if (strReserve.empty())
        strReserve = "1";

    OSRefTableEx* deviceMap = QTSServerInterface::GetServer()->GetDeviceSessionMap();
    OSRefTableEx::OSRefEx* theDevRef = deviceMap->Resolve(strDeviceSerial);
    if (theDevRef == NULL)//找不到指定设备
        return EASY_ERROR_DEVICE_NOT_FOUND;

    OSRefReleaserEx releaser(deviceMap, strDeviceSerial);
    //走到这说明存在指定设备
    HTTPSession* pDevSession = static_cast<HTTPSession *>(theDevRef->GetObjectPtr());//获得当前设备回话

    string errNo, errString;
    if (strCmd == "SENDDATA")
    {
        if (!pDevSession->GetTalkbackSession().empty() && pDevSession->GetTalkbackSession() == fSessionID)
        {
            EasyProtocolACK reqreq(MSG_SD_CONTROL_TALKBACK_REQ);
            EasyJsonValue headerheader, bodybody;

            char chTemp[16] = { 0 };
            UInt32 uDevCseq = pDevSession->GetCSeq();
            sprintf(chTemp, "%d", uDevCseq);
            headerheader[EASY_TAG_CSEQ] = string(chTemp);//注意这个地方不能直接将UINT32->int,因为会造成数据失真
            headerheader[EASY_TAG_VERSION] = EASY_PROTOCOL_VERSION;

            bodybody[EASY_TAG_SERIAL] = strDeviceSerial;
            bodybody[EASY_TAG_CHANNEL] = strChannel;
            bodybody[EASY_TAG_PROTOCOL] = strProtocol;
            bodybody[EASY_TAG_RESERVE] = strReserve;
            bodybody[EASY_TAG_CMD] = strCmd;
            bodybody[EASY_TAG_AUDIO_TYPE] = strAudioType;
            bodybody[EASY_TAG_AUDIO_DATA] = strAudioData;
            bodybody[EASY_TAG_PTS] = strPts;
            bodybody[EASY_TAG_FROM] = fSessionID;
            bodybody[EASY_TAG_TO] = pDevSession->GetValue(EasyHTTPSessionID)->GetAsCString();
            bodybody[EASY_TAG_VIA] = QTSServerInterface::GetServer()->GetCloudServiceNodeID();

            reqreq.SetHead(headerheader);
            reqreq.SetBody(bodybody);

            string buffer = reqreq.GetMsg();
            StrPtrLen theValue(const_cast<char*>(buffer.c_str()), buffer.size());
            pDevSession->SendHTTPPacket(&theValue, false, false);

            errNo = EASY_ERROR_SUCCESS_OK;
            errString = EasyProtocol::GetErrorString(EASY_ERROR_SUCCESS_OK);
        }
        else
        {
            errNo = EASY_ERROR_CLIENT_BAD_REQUEST;
            errString = EasyProtocol::GetErrorString(EASY_ERROR_CLIENT_BAD_REQUEST);
            goto ACK;
        }
    }
    else
    {
        if (strCmd == "START")
        {
            if (pDevSession->GetTalkbackSession().empty())
            {
                pDevSession->SetTalkbackSession(fSessionID);
                errNo = EASY_ERROR_SUCCESS_OK;
                errString = EasyProtocol::GetErrorString(EASY_ERROR_SUCCESS_OK);
            }
            else
            {
                errNo = EASY_ERROR_CLIENT_BAD_REQUEST;
                errString = EasyProtocol::GetErrorString(EASY_ERROR_CLIENT_BAD_REQUEST);
                goto ACK;
            }
        }
        else if (strCmd == "STOP")
        {
            if (pDevSession->GetTalkbackSession().empty() || pDevSession->GetTalkbackSession() != fSessionID)
            {
                errNo = EASY_ERROR_CLIENT_BAD_REQUEST;
                errString = EasyProtocol::GetErrorString(EASY_ERROR_CLIENT_BAD_REQUEST);
                goto ACK;
            }
            else
            {
                pDevSession->SetTalkbackSession("");
                errNo = EASY_ERROR_SUCCESS_OK;
                errString = EasyProtocol::GetErrorString(EASY_ERROR_SUCCESS_OK);
            }
        }


        EasyProtocolACK reqreq(MSG_SD_CONTROL_TALKBACK_REQ);
        EasyJsonValue headerheader, bodybody;

        char chTemp[16] = { 0 };
        UInt32 uDevCseq = pDevSession->GetCSeq();
        sprintf(chTemp, "%d", uDevCseq);
        headerheader[EASY_TAG_CSEQ] = string(chTemp);//注意这个地方不能直接将UINT32->int,因为会造成数据失真
        headerheader[EASY_TAG_VERSION] = EASY_PROTOCOL_VERSION;

        bodybody[EASY_TAG_SERIAL] = strDeviceSerial;
        bodybody[EASY_TAG_CHANNEL] = strChannel;
        bodybody[EASY_TAG_PROTOCOL] = strProtocol;
        bodybody[EASY_TAG_RESERVE] = strReserve;
        bodybody[EASY_TAG_CMD] = strCmd;
        bodybody[EASY_TAG_AUDIO_TYPE] = strAudioType;
        bodybody[EASY_TAG_AUDIO_DATA] = strAudioData;
        bodybody[EASY_TAG_PTS] = strPts;
        bodybody[EASY_TAG_FROM] = fSessionID;
        bodybody[EASY_TAG_TO] = pDevSession->GetValue(EasyHTTPSessionID)->GetAsCString();
        bodybody[EASY_TAG_VIA] = QTSServerInterface::GetServer()->GetCloudServiceNodeID();

        reqreq.SetHead(headerheader);
        reqreq.SetBody(bodybody);

        string buffer = reqreq.GetMsg();
        StrPtrLen theValue(const_cast<char*>(buffer.c_str()), buffer.size());
        pDevSession->SendHTTPPacket(&theValue, false, false);
    }

ACK:
    char chTemp[16] = { 0 };
    UInt32 uDevCseq = pDevSession->GetCSeq();
    sprintf(chTemp, "%d", uDevCseq);

    EasyProtocolACK rsp(MSG_SC_TALKBACK_CONTROL_ACK);
    EasyJsonValue header, body;
    body[EASY_TAG_SERIAL] = strDeviceSerial;
    body[EASY_TAG_CHANNEL] = strChannel;
    body[EASY_TAG_PROTOCOL] = strProtocol;
    body[EASY_TAG_RESERVE] = strReserve;

    header[EASY_TAG_VERSION] = EASY_PROTOCOL_VERSION;
    header[EASY_TAG_CSEQ] = string(chTemp);;
    header[EASY_TAG_ERROR_NUM] = errNo;
    header[EASY_TAG_ERROR_STRING] = errString;

    rsp.SetHead(header);
    rsp.SetBody(body);
    string msg = rsp.GetMsg();
    StrPtrLen theValueAck(const_cast<char*>(msg.c_str()), msg.size());
    this->SendHTTPPacket(&theValueAck, false, false);

    return QTSS_NoErr;
}
//EasyCamera EasyCameraSource.cpp ControlTalkback
QTSS_Error EasyCameraSource::ControlTalkback(Easy_CameraTalkback_Params* params)
{
    QTSS_Error result = QTSS_RequestFailed;

    if (cameraLogin())
    {
        HI_S32 error = HI_SUCCESS;
        switch (params->inCommand)
        {
        case EASY_TALKBACK_CMD_TYPE_START:
            {
                int type = 1;
                if (params->inType == EASY_TALKBACK_AUDIO_TYPE_G711A)
                    type = 1;
                else if (params->inType == EASY_TALKBACK_AUDIO_TYPE_G726)
                    type = 4;
                error = HI_NET_DEV_StartVoice(m_u32Handle, type);
            }
            break;
        case EASY_TALKBACK_CMD_TYPE_SENDDATA:
            {
                if (params->inBuff == NULL || params->inBuffLen == 0)
                {
                    result = QTSS_BadArgument;
                    break;
                }
                int len = params->inBuffLen;
                int offset = 0;
                int pts = params->inPts;
                while (len >= BUFFLEN)
                {
                    AddHead(fTalkbackBuff, (char*)params->inBuff + offset, BUFFLEN);
                    error = HI_NET_DEV_SendVoiceData(m_u32Handle, fTalkbackBuff, HSBUFFLEN, pts);
                    offset += BUFFLEN;
                    len -= BUFFLEN;
                    pts += PTSPER;
                }
            }
            break;
        case EASY_TALKBACK_CMD_TYPE_STOP:
            error = HI_NET_DEV_StopVoice(m_u32Handle);
            break;
        default:
            result = QTSS_RequestFailed;
            break;
        }

        if (error == HI_SUCCESS)
        {
            result = QTSS_NoErr;
        }
        else
        {
            result = QTSS_RequestFailed;
        }
    }

    return result;
}

代码与文档

EasyDarwin Github:https://github.com/easydarwin

EasyDarwin开源流媒体云平台协议文档:https://github.com/EasyDarwin/EasyDarwin/blob/master/Doc/EasyDarwin%20Protocol.pdf

获取更多信息

邮件:support@easydarwin.org

WEB:www.EasyDarwin.org

QQ交流群:496258327

Copyright © EasyDarwin.org 2012-2016

EasyDarwin

本页内容版权归属为原作者,如有侵犯您的权益,请通知我们删除。

Spring事务管理(5)-开启事务 - 2016-07-25 18:07:44

在前几篇文章中,我们分析了Spring的AOP实现,AOP的实现原理即JdkDynamicAop或Cglib。目标方法执行的时候,进入invoke方法中先执行Advisors链。Spring的事务开启需要在目标方法执行前进行,因此可以作为一个前置增强添加到Advisors链中。 Spring的声明式事务配置如下: bean id = "transactionManager" class = "org.springframework.orm.hibernate3.HibernateTransactionMa

Linux之vim学习 - 2016-07-25 18:07:26

vim 分三种模式:一般模式、编辑模式、命令模式 1.一般模式 一般模式下可以进行移动光标、删除、粘贴复制等操作 移动光标操作 h或向左箭头:光标左移动一个字符j或向下箭头:光标下移动一个字符k或向上箭头:光标上移动一个字符l或向右箭头:光标右移动一个字符 30 j:光标下移动 30 个字符ctrl+f:屏幕向下移动一页,相当于Page Down (常用)ctrl+b:屏幕向上移动一页,相当于Page Down (常用)ctrl+d:屏幕向下移动半页ctrl+u:屏幕向上移动半页nspace右移动n个字符
1.DispatchAction-分派Action 1.1 为什么需要DispatchAction 如果每个请求都对应一个Action,就会造成action过多,程序显得比较臃肿,所以可以把一类请求写到一个action中处理,即DispatchAction 在没有使用框架之前,当我们通过一个控制器处理多个请求的时候,我们是通过在URL后面跟上参数来区别不同的操作,比如,下述链接: http://localhost:8080/UserManager/main?operateId=adduid=123 htt

[置顶] 逐步深入TCP/IP协议栈 - 2016-07-25 18:07:17

一、关于应用层用户数据的历程,见下图:                                                                             TCP/IP数据包的封装 过程: 应用层将数据通过协议栈逐层向下传递,其下的每层接到来自上层的数据时,根据每层的协议都要在其数据 的前端添加首部信息进行封装。不同的协议层对数据包有不同 的称谓,在传输层叫做数据段,在网络层叫做数据报, 在链路层叫做数据帧。在经过链路层时,数据已经封装成帧并递交给物理层的传输介质上,到

Linux命令应用大词典 目录 - 2016-07-25 18:07:40

【作者】 於岳 【编辑】 李永涛 【ISBN】 978-7-115-40151-9 【日期】 2015-12 【页数】 703页 【字数】 1123千字 【开本】 16 【定价】 89元 【总数】 本书包含46大类729个命令 按章节分 第1章 登录、退出、关机和重启 章节-页码 命令 介绍 备注 1.1-P1 login 用户登录系统 1.2-P1 logout 退出登录Shell 1.3-P1 nologin 限制用户登录 1.4-P2 exit 退出Shell 1.5-P2 sulogin 单用户登

Dubbo--生态系统安装 - 2016-07-25 18:07:33

dubbo的整套环境主要有几个系统: Zookeeper注册中心安装 管理控制台安装 简易监控中心安装       在 Dubbo官网 中已经讲的很详细。本文主要是为了记录一下自己在安装过程遇到的问题,同时也把安装完成之后的文件包记录 下。       运行环境: tate@ubuntu:~$ uname -aLinux ubuntu 3.19.0-65-generic #73~14.04.1-Ubuntu SMP Wed Jun 29 21:05:22 UTC 2016 x86_64 x86_64 x8
1.使用者与群组以及其他人的概念 假设有一家人,家裡只有三兄弟,分别是王大毛、王二毛与王三毛三个人, 而这个家庭是登记在王大毛的名下的!所以,『王大毛家有三个人,分别是王大毛、王二毛与王三毛』, 而且这三个人都有自己的房间,并且共同拥有一个客厅! 使用者的意义:由于王家三人各自拥有自己的房间,所以, 王二毛虽然可以进入王三毛的房间,但是二毛不能翻三毛的抽屉! 因为抽屉里面面可能有三毛自己私人的东西,例如日记等等的,这是『私人的空间』,所以当然不能让二毛拿!  群组的概念:由于共同拥有客厅,所以王家三兄弟可

理解 glibc malloc - 2016-07-25 17:07:29

本篇文章主要完成了对 《Understanding glibc malloc》 的翻译工作。限于本人翻译水平与专业技术水平(纯粹为了了解内存分配而翻),本文章必定会有很多不足之处,请大家见谅,也欢迎大家的指正! 联系邮箱: 974985526@qq.com 。 堆内存是一个很有意思的领域,这样的问题: 堆内存是如何从内核中分配的? 内存管理效率怎样? 它是由内核、库函数还是应用本身管理的? 堆内存可以开发吗? 我也困惑了很久,但是直到最近我才有时间去了解它。下面就让我来谈谈我的研究成果。开源社区提供了很多
一、别人记得流水账 MAC上装东西总是遇到些问题,这里做个流水帐。希望能对别人有点帮助哈   1、先下载需要的软件包        OCR工具:   Tesseract-OCR3.0.1  source code      tesseract-ocr-3.01.eng.tar.gz  破验证码用英文就够了。        图像处理工具:   Leptonica   1.68        png识别工具:   libpng         jpeg 识别工具 : libjpeg         tif 识
自动化测试框架UFT BASED 自动化测试,一个现在被炒的火热的词;各大公司都在嚷嚷着要上自动化测试的项目,都在招聘各种自动化测试人员。。。 本材料对于编程基础较低初学者,在编写和学习过程中可以接触到很多旁枝侧节的知识,这些都是做好自动化所有需要的知识;对于有一定技术储备。 本框架不能帮你成为高大上的编程大牛,或者自动化测试的行家。但是,它可以引领你迈入自动化测试的领域。 师傅领进门,修行靠个人;一切的一切都还是要靠你自己去多多实践,不是有一句名言么?实践是检验真理的唯一标准! 一、自动化测试基础 手工