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

相关文章

Android中Dialog的使用详解

《Android中Dialog的使用详解》Dialog(对话框)是Android中常用的UI组件,用于临时显示重要信息或获取用户输入,本文给大家介绍Android中Dialog的使用,感兴趣的朋友一起... 目录android中Dialog的使用详解1. 基本Dialog类型1.1 AlertDialog(

go中空接口的具体使用

《go中空接口的具体使用》空接口是一种特殊的接口类型,它不包含任何方法,本文主要介绍了go中空接口的具体使用,具有一定的参考价值,感兴趣的可以了解一下... 目录接口-空接口1. 什么是空接口?2. 如何使用空接口?第一,第二,第三,3. 空接口几个要注意的坑坑1:坑2:坑3:接口-空接口1. 什么是空接

Python Dash框架在数据可视化仪表板中的应用与实践记录

《PythonDash框架在数据可视化仪表板中的应用与实践记录》Python的PlotlyDash库提供了一种简便且强大的方式来构建和展示互动式数据仪表板,本篇文章将深入探讨如何使用Dash设计一... 目录python Dash框架在数据可视化仪表板中的应用与实践1. 什么是Plotly Dash?1.1

基于Flask框架添加多个AI模型的API并进行交互

《基于Flask框架添加多个AI模型的API并进行交互》:本文主要介绍如何基于Flask框架开发AI模型API管理系统,允许用户添加、删除不同AI模型的API密钥,感兴趣的可以了解下... 目录1. 概述2. 后端代码说明2.1 依赖库导入2.2 应用初始化2.3 API 存储字典2.4 路由函数2.5 应

Python GUI框架中的PyQt详解

《PythonGUI框架中的PyQt详解》PyQt是Python语言中最强大且广泛应用的GUI框架之一,基于Qt库的Python绑定实现,本文将深入解析PyQt的核心模块,并通过代码示例展示其应用场... 目录一、PyQt核心模块概览二、核心模块详解与示例1. QtCore - 核心基础模块2. QtWid

Android Kotlin 高阶函数详解及其在协程中的应用小结

《AndroidKotlin高阶函数详解及其在协程中的应用小结》高阶函数是Kotlin中的一个重要特性,它能够将函数作为一等公民(First-ClassCitizen),使得代码更加简洁、灵活和可... 目录1. 引言2. 什么是高阶函数?3. 高阶函数的基础用法3.1 传递函数作为参数3.2 Lambda

Android自定义Scrollbar的两种实现方式

《Android自定义Scrollbar的两种实现方式》本文介绍两种实现自定义滚动条的方法,分别通过ItemDecoration方案和独立View方案实现滚动条定制化,文章通过代码示例讲解的非常详细,... 目录方案一:ItemDecoration实现(推荐用于RecyclerView)实现原理完整代码实现

Android App安装列表获取方法(实践方案)

《AndroidApp安装列表获取方法(实践方案)》文章介绍了Android11及以上版本获取应用列表的方案调整,包括权限配置、白名单配置和action配置三种方式,并提供了相应的Java和Kotl... 目录前言实现方案         方案概述一、 androidManifest 三种配置方式

Spring AI ectorStore的使用流程

《SpringAIectorStore的使用流程》SpringAI中的VectorStore是一种用于存储和检索高维向量数据的数据库或存储解决方案,它在AI应用中发挥着至关重要的作用,本文给大家介... 目录一、VectorStore的基本概念二、VectorStore的核心接口三、VectorStore的

最新Spring Security实战教程之Spring Security安全框架指南

《最新SpringSecurity实战教程之SpringSecurity安全框架指南》SpringSecurity是Spring生态系统中的核心组件,提供认证、授权和防护机制,以保护应用免受各种安... 目录前言什么是Spring Security?同类框架对比Spring Security典型应用场景传统