android多媒体框架之流媒体具体流程篇3----base on jellybean(十三)

本文主要是介绍android多媒体框架之流媒体具体流程篇3----base on jellybean(十三),希望对大家解决编程问题提供一定的参考价值,需要的开发者们随着小编来一起学习吧!

距离上一篇文章好久了,一直没更新上,在此深表歉意。

上一篇我们讲到了从web server 中获取了sessiondescription,并解析出了media server的路径和一些基本的媒体信息。下面我们开始讲述如何跟mediaserver建立连接并控制服务器端和客户端以达到播放,暂停,停止的目的。

首先跟media server建立连接 SETUP:

具体的格式如下(UDP):

C->Aaudio: SETUPrtsp://audio.com/twister/audio.en RTSP/1.0

CSeq: 1

Transport:RTP/AVP/UDP;unicast

;client_port=3056-3057

具体到代码的话,我们看myHandler.h中的setupTrack函数:

   void setupTrack(size_t index) {

        sp<APacketSource> source =

            new APacketSource(mSessionDesc,index);

……………………….

        AString url;

        CHECK(mSessionDesc->findAttribute(index,"a=control", &url));

 

        AString trackURL;

        CHECK(MakeURL(mBaseURL.c_str(),url.c_str(), &trackURL));----检查session description中取出media serverURL是否正确

        …………

 

        AString request= "SETUP ";

       request.append(trackURL);

        request.append("RTSP/1.0\r\n");------拼接request字符

 

选择TCP连接还是ARTP连接,

        if (mTryTCPInterleaving) {

            size_t interleaveIndex = 2 *(mTracks.size() - 1);

            info->mUsingInterleavedTCP =true;

            info->mRTPSocket =interleaveIndex;

            info->mRTCPSocket =interleaveIndex + 1;

 

           request.append("Transport: RTP/AVP/TCP;interleaved=");

           request.append(interleaveIndex);

           request.append("-");

           request.append(interleaveIndex + 1);

        } else {

            unsigned rtpPort;

            ARTPConnection::MakePortPair(

                    &info->mRTPSocket,&info->mRTCPSocket, &rtpPort);

 

            if (mUIDValid) {

               HTTPBase::RegisterSocketUserTag(info->mRTPSocket, mUID,

                                               (uint32_t)*(uint32_t*) "RTP_");

               HTTPBase::RegisterSocketUserTag(info->mRTCPSocket, mUID,

                                                (uint32_t)*(uint32_t*)"RTP_");

            }

 

            request.append("Transport:RTP/AVP/UDP;unicast;client_port=");

           request.append(rtpPort);

           request.append("-");

            request.append(rtpPort+ 1);

        }

 

        request.append("\r\n");

 

        if (index > 1) {

            request.append("Session:");

            request.append(mSessionID);

            request.append("\r\n");

        }

 

        request.append("\r\n");

 

        sp<AMessage> reply = newAMessage('setu', id());

        reply->setSize("index",index);

       reply->setSize("track-index", mTracks.size() - 1);

        mConn->sendRequest(request.c_str(),reply);-----发送给服务器端,等待回复,返回的Amessage是“setu

}

   

 

假设收到服务端的连接成功的消息,我们看看myHandler.h中onMessageReceived对应的”setu”如何处理,按道理应该回复回来的信息如下(UDP):

A->C: RTSP/1.0200 OK

CSeq: 1

Session: 12345678

Transport:RTP/AVP/UDP;unicast

;client_port=3056-3057;

;server_port=5000-5001

 

 

virtualvoid onMessageReceived(const sp<AMessage> &msg) {

……

    case 'setu':

            {

                ……………………….

                int32_t result;

               CHECK(msg->findInt32("result", &result));

 

                ALOGI("SETUP(%d) completedwith result %d (%s)",

                     index, result,strerror(-result));

 

                if (result == OK) {

                    CHECK(track != NULL);

 

                    sp<RefBase> obj;

                    CHECK(msg->findObject("response",&obj));

                    sp<ARTSPResponse>response =

                       static_cast<ARTSPResponse *>(obj.get());

 

                    if(response->mStatusCode != 200) {

                        result = UNKNOWN_ERROR;

                    } else {

                       ssize_t i = response->mHeaders.indexOfKey("session");-------查找session id

                        CHECK_GE(i, 0);

 

                       mSessionID = response->mHeaders.valueAt(i);

 

………………………..

 

                        i =mSessionID.find(";");

                        if (i >= 0) {

                            // Remove options,i.e. ";timeout=90"

                            mSessionID.erase(i,mSessionID.size() - i);

                        }

 

                        i = response->mHeaders.indexOfKey("server");---server

                        if (i >= 0) {

                            AString server =response->mHeaders.valueAt(i);

                            if(server.startsWith("XenonStreamer")

                                    ||server.startsWith("XTream")) {

                                ALOGI("Usefake timestamps");

                                mUseSR = false;

                            }

                        }

 

                        sp<AMessage>notify = new AMessage('accu', id());

                       notify->setSize("track-index", trackIndex);

 

                        i =response->mHeaders.indexOfKey("transport");---transport

                        CHECK_GE(i, 0);

 

                        if(track->mRTPSocket != -1 && track->mRTCPSocket != -1) {

                            if(!track->mUsingInterleavedTCP) {

                                AStringtransport = response->mHeaders.valueAt(i);

 

 

……………….

                ++index;

                if (result == OK &&index < mSessionDesc->countTracks()) {

                    setupTrack(index);----一般有两条track,先是audio track然后是videotrack

                } else if(mSetupTracksSuccessful) {

建立完成后就可以“PLAY”了

                    ++mKeepAliveGeneration;

                    postKeepAlive();

 

                    AStringrequest = "PLAY ";---------发送”PLAY”请求给服务器端

                   request.append(mControlURL);

                   request.append(" RTSP/1.0\r\n");

 

                   request.append("Session: ");

                   request.append(mSessionID);

                    request.append("\r\n");

 

                   request.append("\r\n");

 

                   sp<AMessage> reply = new AMessage('play', id());

                   mConn->sendRequest(request.c_str(), reply);

                } else {

                    sp<AMessage> reply = newAMessage('disc', id());

                   mConn->disconnect(reply);

                }

                break;

            }

 

完成“SETUP”阶段就可以“PLAY”了,发送给服务器端的格式如下:

C->V:PLAY rtsp://video.com/twister/video RTSP/1.0

CSeq: 2

Session:23456789

Range:smpte=0:10:00-

代码在myHandler.h中onMessageReceived对应的”setu”。

下面我们分析下服务器端返回后客户端如何处理“PLAY”。还是在myHandler.h中onMessageReceived函数:

 

            case 'play':

            {

                ………..

 

                if (result == OK) {

                    sp<RefBase> obj;

                   CHECK(msg->findObject("response", &obj));

                    sp<ARTSPResponse>response =

                        static_cast<ARTSPResponse*>(obj.get());

 

                    if(response->mStatusCode != 200) {

                        result = UNKNOWN_ERROR;

                    } else {

                        parsePlayResponse(response);---解析response回来的数据

 

………………

                }

 

                if (result != OK) {

                    sp<AMessage> reply =new AMessage('disc', id());

                   mConn->disconnect(reply);

                }

 

                break;

            }

response回来的格式一般如下:

V->C:RTSP/1.0 200 OK

CSeq: 2

Session:23456789

Range:smpte=0:10:00-0:20:00------------------播放从10分钟到20分钟时间段的视频

RTP-Info:url=rtsp://video.com/twister/video

;seq=12312232;rtptime=78712811

 

 

voidparsePlayResponse(const sp<ARTSPResponse> &response) {

        if (mTracks.size() == 0) {

            ALOGV("parsePlayResponse: latepackets ignored.");

            return;

        }

 

        mPlayResponseReceived = true;

 

        ssize_t i =response->mHeaders.indexOfKey("range");

…………

        AString range = response->mHeaders.valueAt(i);

………………

 

        i =response->mHeaders.indexOfKey("rtp-info");

        CHECK_GE(i, 0);

 

        AString rtpInfo =response->mHeaders.valueAt(i);

        List<AString> streamInfos;

        SplitString(rtpInfo, ",",&streamInfos);

 

        int n = 1;

        for (List<AString>::iterator it =streamInfos.begin();

             it != streamInfos.end(); ++it) {

            (*it).trim();

            ALOGV("streamInfo[%d] =%s", n, (*it).c_str());

 

            CHECK(GetAttribute((*it).c_str(),"url", &val));

 

            size_t trackIndex = 0;

            while (trackIndex <mTracks.size()) {

                size_t startpos = 0;

                if(mTracks.editItemAt(trackIndex).mURL.size() >= val.size()) {

                    startpos =mTracks.editItemAt(trackIndex).mURL.size() - val.size();

                }

                // Use AString::find in orderto allow the url in the RTP-Info to be a

                // truncated variant (example:"url=trackID=1") of the complete SETUP url

                if(mTracks.editItemAt(trackIndex).mURL.find(val.c_str(), startpos) == -1) {

                    ++trackIndex;

                } else {

                    // Found track

                    break;

                }

            }

            CHECK_LT(trackIndex,mTracks.size());

 

            char *end;

            unsigned long seq = 0;

            if (GetAttribute((*it).c_str(),"seq", &val)) {

                seq = strtoul(val.c_str(),&end, 10);

            } else {

               CHECK(GetAttribute((*it).c_str(), "rtptime", &val));

            }

 

            TrackInfo *info = &mTracks.editItemAt(trackIndex);

            info->mFirstSeqNumInSegment =seq;

            info->mNewSegment = true;

 

            uint32_t rtpTime = 0;

            if (GetAttribute((*it).c_str(),"rtptime", &val)) {

                rtpTime = strtoul(val.c_str(),&end, 10);

                mReceivedRTPTime = true;

                ALOGV("track #%d:rtpTime=%u <=> npt=%.2f", n, rtpTime, npt1);

            } else {

                ALOGV("no rtptime in playresponse: track #%d: rtpTime=%u <=> npt=%.2f", n,

                        rtpTime, npt1);

               CHECK(GetAttribute((*it).c_str(), "seq", &val));

            }

 

            info->mRTPAnchor = rtpTime;

            mLastMediaTimeUs = (int64_t)(npt1 *1E6);

            mMediaAnchorUs = mLastMediaTimeUs;

 

            // Removing packets with old RTPtimestamps

            while (!info->mPackets.empty()){

                sp<ABuffer> accessUnit =*info->mPackets.begin();

                uint32_t firstRtpTime;

               CHECK(accessUnit->meta()->findInt32("rtp-time", (int32_t*)&firstRtpTime));

                if (firstRtpTime == rtpTime) {

                    break;

                }

               info->mPackets.erase(info->mPackets.begin());

            }

            ++n;

        }

   

 

至此video source 和audiosource就可以通过RTP不断的往客户端发送,客户端拿到这些数据就可以通过相应的解码器解析播放了。

我们的流媒体播放流程也讲得差不多了,如何关闭两端的流程就由大伙自己去看了。但是大家要注意一点有时候一些服务在关闭的时候没有发回“ TEARDOWN ”的 response。

这篇关于android多媒体框架之流媒体具体流程篇3----base on jellybean(十三)的文章就介绍到这儿,希望我们推荐的文章对编程师们有所帮助!



http://www.chinasem.cn/article/897607

相关文章

Nginx分布式部署流程分析

《Nginx分布式部署流程分析》文章介绍Nginx在分布式部署中的反向代理和负载均衡作用,用于分发请求、减轻服务器压力及解决session共享问题,涵盖配置方法、策略及Java项目应用,并提及分布式事... 目录分布式部署NginxJava中的代理代理分为正向代理和反向代理正向代理反向代理Nginx应用场景

Spring Boot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)

《SpringBoot分层架构详解之从Controller到Service再到Mapper的完整流程(用户管理系统为例)》本文将以一个实际案例(用户管理系统)为例,详细解析SpringBoot中Co... 目录引言:为什么学习Spring Boot分层架构?第一部分:Spring Boot的整体架构1.1

nodejs打包作为公共包使用的完整流程

《nodejs打包作为公共包使用的完整流程》在Node.js项目中,打包和部署是发布应用的关键步骤,:本文主要介绍nodejs打包作为公共包使用的相关资料,文中通过代码介绍的非常详细,需要的朋友可... 目录前言一、前置准备二、创建与编码三、一键构建四、本地“白嫖”测试(可选)五、发布公共包六、常见踩坑提醒

Ubuntu向多台主机批量传输文件的流程步骤

《Ubuntu向多台主机批量传输文件的流程步骤》:本文主要介绍在Ubuntu中批量传输文件到多台主机的方法,需确保主机互通、用户名密码统一及端口开放,通过安装sshpass工具,准备包含目标主机信... 目录Ubuntu 向多台主机批量传输文件1.安装 sshpass2.准备主机列表文件3.创建一个批处理脚

Java 缓存框架 Caffeine 应用场景解析

《Java缓存框架Caffeine应用场景解析》文章介绍Caffeine作为高性能Java本地缓存框架,基于W-TinyLFU算法,支持异步加载、灵活过期策略、内存安全机制及统计监控,重点解析其... 目录一、Caffeine 简介1. 框架概述1.1 Caffeine的核心优势二、Caffeine 基础2

python pymodbus模块的具体使用

《pythonpymodbus模块的具体使用》pymodbus是一个Python实现的Modbus协议库,支持TCP和RTU通信模式,支持读写线圈、离散输入、保持寄存器等数据类型,具有一定的参考价值... 目录一、详解1、 基础概念2、核心功能3、安装与设置4、使用示例5、 高级特性6、注意事项二、代码示例

一个Java的main方法在JVM中的执行流程示例详解

《一个Java的main方法在JVM中的执行流程示例详解》main方法是Java程序的入口点,程序从这里开始执行,:本文主要介绍一个Java的main方法在JVM中执行流程的相关资料,文中通过代码... 目录第一阶段:加载 (Loading)第二阶段:链接 (Linking)第三阶段:初始化 (Initia

Git打标签从本地创建到远端推送的详细流程

《Git打标签从本地创建到远端推送的详细流程》在软件开发中,Git标签(Tag)是为发布版本、标记里程碑量身定制的“快照锚点”,它能永久记录项目历史中的关键节点,然而,仅创建本地标签往往不够,如何将其... 目录一、标签的两种“形态”二、本地创建与查看1. 打附注标http://www.chinasem.cn

Android实现图片浏览功能的示例详解(附带源码)

《Android实现图片浏览功能的示例详解(附带源码)》在许多应用中,都需要展示图片并支持用户进行浏览,本文主要为大家介绍了如何通过Android实现图片浏览功能,感兴趣的小伙伴可以跟随小编一起学习一... 目录一、项目背景详细介绍二、项目需求详细介绍三、相关技术详细介绍四、实现思路详细介绍五、完整实现代码

在Android中使用WebView在线查看PDF文件的方法示例

《在Android中使用WebView在线查看PDF文件的方法示例》在Android应用开发中,有时我们需要在客户端展示PDF文件,以便用户可以阅读或交互,:本文主要介绍在Android中使用We... 目录简介:1. WebView组件介绍2. 在androidManifest.XML中添加Interne