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

相关文章

Security OAuth2 单点登录流程

单点登录(英语:Single sign-on,缩写为 SSO),又译为单一签入,一种对于许多相互关连,但是又是各自独立的软件系统,提供访问控制的属性。当拥有这项属性时,当用户登录时,就可以获取所有系统的访问权限,不用对每个单一系统都逐一登录。这项功能通常是以轻型目录访问协议(LDAP)来实现,在服务器上会将用户信息存储到LDAP数据库中。相同的,单一注销(single sign-off)就是指

Spring Security基于数据库验证流程详解

Spring Security 校验流程图 相关解释说明(认真看哦) AbstractAuthenticationProcessingFilter 抽象类 /*** 调用 #requiresAuthentication(HttpServletRequest, HttpServletResponse) 决定是否需要进行验证操作。* 如果需要验证,则会调用 #attemptAuthentica

Android实现任意版本设置默认的锁屏壁纸和桌面壁纸(两张壁纸可不一致)

客户有些需求需要设置默认壁纸和锁屏壁纸  在默认情况下 这两个壁纸是相同的  如果需要默认的锁屏壁纸和桌面壁纸不一样 需要额外修改 Android13实现 替换默认桌面壁纸: 将图片文件替换frameworks/base/core/res/res/drawable-nodpi/default_wallpaper.*  (注意不能是bmp格式) 替换默认锁屏壁纸: 将图片资源放入vendo

Android平台播放RTSP流的几种方案探究(VLC VS ExoPlayer VS SmartPlayer)

技术背景 好多开发者需要遴选Android平台RTSP直播播放器的时候,不知道如何选的好,本文针对常用的方案,做个大概的说明: 1. 使用VLC for Android VLC Media Player(VLC多媒体播放器),最初命名为VideoLAN客户端,是VideoLAN品牌产品,是VideoLAN计划的多媒体播放器。它支持众多音频与视频解码器及文件格式,并支持DVD影音光盘,VCD影

cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个?

跨平台系列 cross-plateform 跨平台应用程序-01-概览 cross-plateform 跨平台应用程序-02-有哪些主流技术栈? cross-plateform 跨平台应用程序-03-如果只选择一个框架,应该选择哪一个? cross-plateform 跨平台应用程序-04-React Native 介绍 cross-plateform 跨平台应用程序-05-Flutte

Spring框架5 - 容器的扩展功能 (ApplicationContext)

private static ApplicationContext applicationContext;static {applicationContext = new ClassPathXmlApplicationContext("bean.xml");} BeanFactory的功能扩展类ApplicationContext进行深度的分析。ApplicationConext与 BeanF

android-opencv-jni

//------------------start opencv--------------------@Override public void onResume(){ super.onResume(); //通过OpenCV引擎服务加载并初始化OpenCV类库,所谓OpenCV引擎服务即是 //OpenCV_2.4.3.2_Manager_2.4_*.apk程序包,存

从状态管理到性能优化:全面解析 Android Compose

文章目录 引言一、Android Compose基本概念1.1 什么是Android Compose?1.2 Compose的优势1.3 如何在项目中使用Compose 二、Compose中的状态管理2.1 状态管理的重要性2.2 Compose中的状态和数据流2.3 使用State和MutableState处理状态2.4 通过ViewModel进行状态管理 三、Compose中的列表和滚动

数据治理框架-ISO数据治理标准

引言 "数据治理"并不是一个新的概念,国内外有很多组织专注于数据治理理论和实践的研究。目前国际上,主要的数据治理框架有ISO数据治理标准、GDI数据治理框架、DAMA数据治理管理框架等。 ISO数据治理标准 改标准阐述了数据治理的标准、基本原则和数据治理模型,是一套完整的数据治理方法论。 ISO/IEC 38505标准的数据治理方法论的核心内容如下: 数据治理的目标:促进组织高效、合理地

Android 10.0 mtk平板camera2横屏预览旋转90度横屏拍照图片旋转90度功能实现

1.前言 在10.0的系统rom定制化开发中,在进行一些平板等默认横屏的设备开发的过程中,需要在进入camera2的 时候,默认预览图像也是需要横屏显示的,在上一篇已经实现了横屏预览功能,然后发现横屏预览后,拍照保存的图片 依然是竖屏的,所以说同样需要将图片也保存为横屏图标了,所以就需要看下mtk的camera2的相关横屏保存图片功能, 如何实现实现横屏保存图片功能 如图所示: 2.mtk