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

相关文章

GSON框架下将百度天气JSON数据转JavaBean

《GSON框架下将百度天气JSON数据转JavaBean》这篇文章主要为大家详细介绍了如何在GSON框架下实现将百度天气JSON数据转JavaBean,文中的示例代码讲解详细,感兴趣的小伙伴可以了解下... 目录前言一、百度天气jsON1、请求参数2、返回参数3、属性映射二、GSON属性映射实战1、类对象映

redis-sentinel基础概念及部署流程

《redis-sentinel基础概念及部署流程》RedisSentinel是Redis的高可用解决方案,通过监控主从节点、自动故障转移、通知机制及配置提供,实现集群故障恢复与服务持续可用,核心组件包... 目录一. 引言二. 核心功能三. 核心组件四. 故障转移流程五. 服务部署六. sentinel部署

解决升级JDK报错:module java.base does not“opens java.lang.reflect“to unnamed module问题

《解决升级JDK报错:modulejava.basedoesnot“opensjava.lang.reflect“tounnamedmodule问题》SpringBoot启动错误源于Jav... 目录问题描述原因分析解决方案总结问题描述启动sprintboot时报以下错误原因分析编程异js常是由Ja

SpringBoot集成XXL-JOB实现任务管理全流程

《SpringBoot集成XXL-JOB实现任务管理全流程》XXL-JOB是一款轻量级分布式任务调度平台,功能丰富、界面简洁、易于扩展,本文介绍如何通过SpringBoot项目,使用RestTempl... 目录一、前言二、项目结构简述三、Maven 依赖四、Controller 代码详解五、Service

Android协程高级用法大全

《Android协程高级用法大全》这篇文章给大家介绍Android协程高级用法大全,本文结合实例代码给大家介绍的非常详细,对大家的学习或工作具有一定的参考借鉴价值,需要的朋友跟随小编一起学习吧... 目录1️⃣ 协程作用域(CoroutineScope)与生命周期绑定Activity/Fragment 中手

Java 中编码与解码的具体实现方法

《Java中编码与解码的具体实现方法》在Java中,字符编码与解码是处理数据的重要组成部分,正确的编码和解码可以确保字符数据在存储、传输、读取时不会出现乱码,本文将详细介绍Java中字符编码与解码的... 目录Java 中编码与解码的实现详解1. 什么是字符编码与解码?1.1 字符编码(Encoding)1

MySQL 临时表与复制表操作全流程案例

《MySQL临时表与复制表操作全流程案例》本文介绍MySQL临时表与复制表的区别与使用,涵盖生命周期、存储机制、操作限制、创建方法及常见问题,本文结合实例代码给大家介绍的非常详细,感兴趣的朋友跟随小... 目录一、mysql 临时表(一)核心特性拓展(二)操作全流程案例1. 复杂查询中的临时表应用2. 临时

C#中SortedSet的具体使用

《C#中SortedSet的具体使用》SortedSet是.NETFramework4.0引入的一个泛型集合类,它实现了一个自动排序的集合,内部使用红黑树数据结构来维护元素的有序性,下面就来介绍一下如... 目录基础概念主要特性创建和初始化基本创建方式自定义比较器基本操作添加和删除元素查询操作范围查询集合运

C# Opacity 不透明度的具体使用

《C#Opacity不透明度的具体使用》本文主要介绍了C#Opacity不透明度的具体使用,文中通过示例代码介绍的非常详细,对大家的学习或者工作具有一定的参考学习价值,需要的朋友们下面随着小编来一... 目录WinFormsOpacity以下是一些使用Opacity属性的示例:设置窗体的透明度:设置按钮的透

Android 缓存日志Logcat导出与分析最佳实践

《Android缓存日志Logcat导出与分析最佳实践》本文全面介绍AndroidLogcat缓存日志的导出与分析方法,涵盖按进程、缓冲区类型及日志级别过滤,自动化工具使用,常见问题解决方案和最佳实... 目录android 缓存日志(Logcat)导出与分析全攻略为什么要导出缓存日志?按需过滤导出1. 按