author | Benjamin Chen <bechen@mozilla.com> |
Mon, 23 Sep 2013 18:12:11 +0800 | |
changeset 164579 | 13746e3db7caf27555f126fe5fc7dd7eccb442b0 |
parent 164578 | 0df04ffc22467ac7c328a6a8219b27aedf891c6c |
child 164580 | 59fb4a13900786755c36b9c4175f4e2704ebccc8 |
push id | 3066 |
push user | akeybl@mozilla.com |
push date | Mon, 09 Dec 2013 19:58:46 +0000 |
treeherder | mozilla-beta@a31a0dce83aa [default view] [failures only] |
perfherder | [talos] [build metrics] [platform microbench] (compared to previous push) |
reviewers | jduell |
bugs | 831645 |
milestone | 27.0a1 |
first release with | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
last release without | nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
|
--- a/b2g/installer/package-manifest.in +++ b/b2g/installer/package-manifest.in @@ -263,16 +263,19 @@ @BINPATH@/components/necko_http.xpt @BINPATH@/components/necko_res.xpt @BINPATH@/components/necko_socket.xpt @BINPATH@/components/necko_strconv.xpt @BINPATH@/components/necko_viewsource.xpt @BINPATH@/components/necko_websocket.xpt @BINPATH@/components/necko_wifi.xpt @BINPATH@/components/necko_wyciwyg.xpt +#ifdef MOZ_RTSP +@BINPATH@/components/necko_rtsp.xpt +#endif @BINPATH@/components/necko.xpt @BINPATH@/components/loginmgr.xpt @BINPATH@/components/parentalcontrols.xpt #ifdef MOZ_WEBRTC @BINPATH@/components/peerconnection.xpt #endif @BINPATH@/components/places.xpt @BINPATH@/components/plugin.xpt
--- a/content/media/moz.build +++ b/content/media/moz.build @@ -77,16 +77,17 @@ EXPORTS += [ 'MediaDecoderOwner.h', 'MediaDecoderReader.h', 'MediaDecoderStateMachine.h', 'MediaMetadataManager.h', 'MediaRecorder.h', 'MediaResource.h', 'MediaSegment.h', 'MediaStreamGraph.h', + 'RtspMediaResource.h', 'SharedBuffer.h', 'StreamBuffer.h', 'TimeVarying.h', 'TrackUnionStream.h', 'VideoFrameContainer.h', 'VideoSegment.h', 'VideoUtils.h', 'VorbisUtils.h', @@ -124,16 +125,17 @@ CPP_SOURCES += [ 'MediaCache.cpp', 'MediaDecoder.cpp', 'MediaDecoderReader.cpp', 'MediaDecoderStateMachine.cpp', 'MediaRecorder.cpp', 'MediaResource.cpp', 'MediaStreamGraph.cpp', 'MediaStreamTrack.cpp', + 'RtspMediaResource.cpp', 'StreamBuffer.cpp', 'TextTrack.cpp', 'TextTrackCue.cpp', 'TextTrackCueList.cpp', 'TextTrackList.cpp', 'TextTrackRegion.cpp', 'TextTrackRegionList.cpp', 'VideoFrameContainer.cpp',
--- a/content/media/omx/moz.build +++ b/content/media/omx/moz.build @@ -13,12 +13,22 @@ EXPORTS += [ CPP_SOURCES += [ 'MediaOmxDecoder.cpp', 'MediaOmxReader.cpp', 'OmxDecoder.cpp', 'OMXCodecProxy.cpp', ] +if CONFIG['MOZ_RTSP']: + EXPORTS += [ + 'RtspOmxDecoder.h', + 'RtspOmxReader.h', + ] + CPP_SOURCES += [ + 'RtspOmxDecoder.cpp', + 'RtspOmxReader.cpp', + ] + LIBXUL_LIBRARY = True LIBRARY_NAME = 'gkconomx_s'
--- a/layout/build/nsLayoutModule.cpp +++ b/layout/build/nsLayoutModule.cpp @@ -225,16 +225,17 @@ static void Shutdown(); #include "nsIMediaManager.h" #include "nsMixedContentBlocker.h" #include "AudioChannelService.h" #include "mozilla/dom/power/PowerManagerService.h" #include "mozilla/dom/alarm/AlarmHalService.h" #include "mozilla/dom/time/TimeService.h" +#include "StreamingProtocolService.h" #include "mozilla/dom/telephony/TelephonyFactory.h" #include "nsITelephonyProvider.h" #ifdef MOZ_WIDGET_GONK #include "GonkGPSGeolocationProvider.h" #endif #include "MediaManager.h" @@ -246,16 +247,17 @@ using namespace mozilla::dom::telephony; using mozilla::dom::alarm::AlarmHalService; using mozilla::dom::indexedDB::IndexedDatabaseManager; using mozilla::dom::power::PowerManagerService; using mozilla::dom::quota::QuotaManager; using mozilla::dom::TCPSocketChild; using mozilla::dom::TCPSocketParent; using mozilla::dom::TCPServerSocketChild; using mozilla::dom::time::TimeService; +using mozilla::net::StreamingProtocolControllerService; // Transformiix /* 5d5d92cd-6bf8-11d9-bf4a-000a95dc234c */ #define TRANSFORMIIX_NODESET_CID \ { 0x5d5d92cd, 0x6bf8, 0x11d9, { 0xbf, 0x4a, 0x0, 0x0a, 0x95, 0xdc, 0x23, 0x4c } } #define TRANSFORMIIX_NODESET_CONTRACTID \ "@mozilla.org/transformiix-nodeset;1" @@ -323,16 +325,19 @@ NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIMobileMessageDatabaseService, SmsServicesFactory::CreateMobileMessageDatabaseService) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIPowerManagerService, PowerManagerService::GetInstance) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIAlarmHalService, AlarmHalService::GetInstance) NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsITimeService, TimeService::GetInstance) +NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIStreamingProtocolControllerService, + StreamingProtocolControllerService::GetInstance) + #ifdef MOZ_GAMEPAD using mozilla::dom::GamepadServiceTest; NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(GamepadServiceTest, GamepadServiceTest::CreateService) #endif #ifdef MOZ_WIDGET_GONK NS_GENERIC_FACTORY_SINGLETON_CONSTRUCTOR(nsIGeolocationProvider, @@ -799,16 +804,17 @@ NS_DEFINE_NAMED_CID(MOBILE_MESSAGE_SERVI NS_DEFINE_NAMED_CID(MOBILE_MESSAGE_DATABASE_SERVICE_CID); NS_DEFINE_NAMED_CID(NS_POWERMANAGERSERVICE_CID); NS_DEFINE_NAMED_CID(OSFILECONSTANTSSERVICE_CID); NS_DEFINE_NAMED_CID(NS_ALARMHALSERVICE_CID); NS_DEFINE_NAMED_CID(TCPSOCKETCHILD_CID); NS_DEFINE_NAMED_CID(TCPSOCKETPARENT_CID); NS_DEFINE_NAMED_CID(TCPSERVERSOCKETCHILD_CID); NS_DEFINE_NAMED_CID(NS_TIMESERVICE_CID); +NS_DEFINE_NAMED_CID(NS_MEDIASTREAMCONTROLLERSERVICE_CID); #ifdef MOZ_WIDGET_GONK NS_DEFINE_NAMED_CID(GONK_GPS_GEOLOCATION_PROVIDER_CID); #endif NS_DEFINE_NAMED_CID(NS_MEDIAMANAGERSERVICE_CID); #ifdef MOZ_GAMEPAD NS_DEFINE_NAMED_CID(NS_GAMEPAD_TEST_CID); #endif #ifdef MOZ_WEBSPEECH @@ -1088,16 +1094,17 @@ static const mozilla::Module::CIDEntry k { &kMOBILE_MESSAGE_DATABASE_SERVICE_CID, false, nullptr, nsIMobileMessageDatabaseServiceConstructor }, { &kNS_POWERMANAGERSERVICE_CID, false, nullptr, nsIPowerManagerServiceConstructor }, { &kOSFILECONSTANTSSERVICE_CID, true, nullptr, OSFileConstantsServiceConstructor }, { &kNS_ALARMHALSERVICE_CID, false, nullptr, nsIAlarmHalServiceConstructor }, { &kTCPSOCKETCHILD_CID, false, nullptr, TCPSocketChildConstructor }, { &kTCPSOCKETPARENT_CID, false, nullptr, TCPSocketParentConstructor }, { &kTCPSERVERSOCKETCHILD_CID, false, nullptr, TCPServerSocketChildConstructor }, { &kNS_TIMESERVICE_CID, false, nullptr, nsITimeServiceConstructor }, + { &kNS_MEDIASTREAMCONTROLLERSERVICE_CID, false, NULL, nsIStreamingProtocolControllerServiceConstructor }, #ifdef MOZ_WIDGET_GONK { &kGONK_GPS_GEOLOCATION_PROVIDER_CID, false, nullptr, nsIGeolocationProviderConstructor }, #endif { &kNS_MEDIAMANAGERSERVICE_CID, false, nullptr, nsIMediaManagerServiceConstructor }, #ifdef MOZ_GAMEPAD { &kNS_GAMEPAD_TEST_CID, false, nullptr, GamepadServiceTestConstructor }, #endif #ifdef ACCESSIBILITY @@ -1244,16 +1251,17 @@ static const mozilla::Module::ContractID { MOBILE_MESSAGE_DATABASE_SERVICE_CONTRACTID, &kMOBILE_MESSAGE_DATABASE_SERVICE_CID }, { POWERMANAGERSERVICE_CONTRACTID, &kNS_POWERMANAGERSERVICE_CID }, { OSFILECONSTANTSSERVICE_CONTRACTID, &kOSFILECONSTANTSSERVICE_CID }, { ALARMHALSERVICE_CONTRACTID, &kNS_ALARMHALSERVICE_CID }, { "@mozilla.org/tcp-socket-child;1", &kTCPSOCKETCHILD_CID }, { "@mozilla.org/tcp-socket-parent;1", &kTCPSOCKETPARENT_CID }, { "@mozilla.org/tcp-server-socket-child;1", &kTCPSERVERSOCKETCHILD_CID }, { TIMESERVICE_CONTRACTID, &kNS_TIMESERVICE_CID }, + { MEDIASTREAMCONTROLLERSERVICE_CONTRACTID, &kNS_MEDIASTREAMCONTROLLERSERVICE_CID }, #ifdef MOZ_WIDGET_GONK { GONK_GPS_GEOLOCATION_PROVIDER_CONTRACTID, &kGONK_GPS_GEOLOCATION_PROVIDER_CID }, #endif #ifdef MOZ_GAMEPAD { NS_GAMEPAD_TEST_CONTRACTID, &kNS_GAMEPAD_TEST_CID }, #endif { MEDIAMANAGERSERVICE_CONTRACTID, &kNS_MEDIAMANAGERSERVICE_CID }, #ifdef ACCESSIBILITY
--- a/netwerk/base/public/moz.build +++ b/netwerk/base/public/moz.build @@ -90,16 +90,18 @@ XPIDL_SOURCES += [ 'nsISocketTransport.idl', 'nsISocketTransportService.idl', 'nsISpeculativeConnect.idl', 'nsIStandardURL.idl', 'nsIStreamListener.idl', 'nsIStreamListenerTee.idl', 'nsIStreamLoader.idl', 'nsIStreamTransportService.idl', + 'nsIStreamingProtocolController.idl', + 'nsIStreamingProtocolService.idl', 'nsISyncStreamListener.idl', 'nsISystemProxySettings.idl', 'nsIThreadRetargetableRequest.idl', 'nsIThreadRetargetableStreamListener.idl', 'nsITimedChannel.idl', 'nsITraceableChannel.idl', 'nsITransport.idl', 'nsIUDPServerSocket.idl',
new file mode 100644 --- /dev/null +++ b/netwerk/base/public/nsIStreamingProtocolController.idl @@ -0,0 +1,179 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set sw=4 ts=4 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ +interface nsIURI; + +#include "nsISupports.idl" + +%{C++ +#define MEDIASTREAM_FRAMETYPE_NORMAL 0x00000001 +#define MEDIASTREAM_FRAMETYPE_DISCONTINUITY 0x00000002 +%} + +/** + * Metadata of the media stream. + */ +[uuid(294adb30-856c-11e2-9e96-0800200c9a66)] +interface nsIStreamingProtocolMetaData : nsISupports +{ + /** + * Frame type. + */ + attribute uint32_t frameType; + + /** + * The total tracks for the given media stream session. + */ + attribute uint32_t totalTracks; + + /** + * The mime type of the track. + */ + attribute ACString mimeType; + + /** + * The width of the resolution. + */ + attribute unsigned long width; + + /** + * The height of the resolution. + */ + attribute unsigned long height; + + /** + * The duration of the media stream. + */ + attribute unsigned long long duration; + + /** + * The sample rate of the media stream. + */ + attribute unsigned long sampleRate; + + /** + * The timestamp indicates the stream absolute position + * relative to the beginning of the presentation. + */ + attribute unsigned long long timeStamp; + + /** + * The total number of audio channels in the media stream. + */ + attribute unsigned long channelCount; + + /** + * The AAC audio codec specific data. + */ + attribute ACString esdsData; + + /** + * The AVCC format extradata of H.264 stream. + */ + attribute ACString avccData; +}; + +/** + * nsIStreamingProtocolListener + */ +[scriptable, uuid(c4f6b660-892e-11e2-9e96-0800200c9a66)] +interface nsIStreamingProtocolListener : nsISupports +{ + /** + * Called when the data may be read without blocking the calling thread. + * @param index The track number of the media stream. + * @param data Raw data of the media stream on given track number. + * @param length The length of the raw data. + * @param offset The offset in the data stream from the start of the media + * presentation in bytes. + * @param meta The meta data of the frame. + */ + void onMediaDataAvailable(in uint8_t index, + in ACString data, + in uint32_t length, + in uint32_t offset, + in nsIStreamingProtocolMetaData meta); + + /** + * Called when the meta data for a given session is available. + * @param index The track number of the media stream. + * @param meta The meta data of the media stream. + */ + void onConnected(in uint8_t index, in nsIStreamingProtocolMetaData meta); + + /** + * Called when the Rtsp session is closed. + * @param index Track number of the media stream. + * @param reason The reason of disconnection. + */ + void onDisconnected(in uint8_t index, in uint32_t reason); +}; + +/** + * Media stream controller API: control and retrieve meta data from media stream. + */ +[uuid(a9bdd4b0-8559-11e2-9e96-0800200c9a66)] +interface nsIStreamingProtocolController : nsISupports +{ + /** + * Preprare the URI before we can start the connection. + * @param aUri The URI of the media stream. + */ + void init(in nsIURI aUri); + + /** + * Asynchronously open this controller. Data is fed to the specified + * media stream listener as it becomes available. If asyncOpen returns + * successfully, the controller is responsible for keeping itself alive + * until it has called onStopRequest on aListener. + * + * @param aListener The nsIStreamingProtocolListener implementation + */ + void asyncOpen(in nsIStreamingProtocolListener aListener); + + /* + * Get the metadata of a track. + * @param index Index of a track. + * @return A nsIStreamingProtocolMetaData. + */ + nsIStreamingProtocolMetaData getTrackMetaData(in octet index); + + /* + * Tell the streaming server to start sending media data. + */ + void play(); + + /* + * Tell the streaming server to pause sending media data. + */ + void pause(); + + /* + * Tell the streaming server to resume the suspended media stream. + */ + void resume(); + + /* + * Tell the streaming server to suspend the media stream. + */ + void suspend(); + + /* + * Tell the streaming server to send media data in specific time. + * @param seekTimeUs Start time of the media stream in microseconds. + */ + void seek(in unsigned long long seekTimeUs); + + /* + * Tell the streaming server to stop the + * media stream and close the connection. + */ + void stop(); + + /** + * Total number of audio/video tracks. + */ + readonly attribute octet totalTracks; +};
new file mode 100644 --- /dev/null +++ b/netwerk/base/public/nsIStreamingProtocolService.idl @@ -0,0 +1,35 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 4 -*- */ +/* vim: set sw=4 ts=4 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +interface nsIStreamingProtocolController; +interface nsIChannel; + +#include "nsISupports.idl" + +%{C++ +#define NS_MEDIASTREAMCONTROLLERSERVICE_CID \ + { 0x94838530, 0x8627, 0x11e2, \ + { \ + 0x9e, 0x96, 0x08, 0x00, \ + 0x20, 0x0c, 0x9a, 0x66 \ + } \ + } +#define MEDIASTREAMCONTROLLERSERVICE_CONTRACTID \ + "@mozilla.org/mediastream/mediastreamcontrollerservice;1" +%} + +/** + * Media stream controller Service API. + */ +[uuid(94838530-8627-11e2-9e96-0800200c9a66)] +interface nsIStreamingProtocolControllerService : nsISupports +{ + /* + * Create a new media stream controller from the given channel. + * @param channel nsIChannel for the given URI. + */ + nsIStreamingProtocolController create(in nsIChannel channel); +};
--- a/netwerk/base/src/Makefile.in +++ b/netwerk/base/src/Makefile.in @@ -1,16 +1,21 @@ # vim:set ts=8 sw=8 sts=8 noet: # # This Source Code Form is subject to the terms of the Mozilla Public # License, v. 2.0. If a copy of the MPL was not distributed with this # file, You can obtain one at http://mozilla.org/MPL/2.0/. LOCAL_INCLUDES += -I$(topsrcdir)/dom/base +ifdef MOZ_RTSP +LOCAL_INCLUDES += -I$(topsrcdir)/netwerk/protocol/rtsp/controller +LOCAL_INCLUDES += -I$(topsrcdir)/netwerk/protocol/rtsp/rtsp +endif + ifdef MOZ_ENABLE_QTNETWORK LOCAL_INCLUDES += -I$(srcdir)/../../system/qt OS_INCLUDES += $(MOZ_QT_CFLAGS) endif include $(topsrcdir)/config/rules.mk include $(topsrcdir)/ipc/chromium/chromium-config.mk
new file mode 100644 --- /dev/null +++ b/netwerk/base/src/StreamingProtocolService.cpp @@ -0,0 +1,71 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "base/basictypes.h" +#include "mozilla/ClearOnShutdown.h" +#include "StreamingProtocolService.h" +#include "mozilla/net/NeckoChild.h" +#include "nsIURI.h" + +#ifdef MOZ_RTSP +#include "RtspControllerChild.h" +#include "RtspController.h" +#endif + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS1(StreamingProtocolControllerService, + nsIStreamingProtocolControllerService) + +/* static */ +StaticRefPtr<StreamingProtocolControllerService> sSingleton; + +/* static */ +already_AddRefed<StreamingProtocolControllerService> +StreamingProtocolControllerService::GetInstance() +{ + if (!sSingleton) { + sSingleton = new StreamingProtocolControllerService(); + ClearOnShutdown(&sSingleton); + } + nsRefPtr<StreamingProtocolControllerService> service = sSingleton.get(); + return service.forget(); +} + +NS_IMETHODIMP +StreamingProtocolControllerService::Create(nsIChannel *aChannel, nsIStreamingProtocolController **aResult) +{ + nsRefPtr<nsIStreamingProtocolController> mediacontroller; + nsCOMPtr<nsIURI> uri; + nsAutoCString scheme; + + NS_ENSURE_ARG_POINTER(aChannel); + aChannel->GetURI(getter_AddRefs(uri)); + + nsresult rv = uri->GetScheme(scheme); + if (NS_FAILED(rv)) return rv; + +#ifdef MOZ_RTSP + if (scheme.EqualsLiteral("rtsp")) { + if (IsNeckoChild()) { + mediacontroller = new RtspControllerChild(aChannel); + } else { + mediacontroller = new RtspController(aChannel); + } + } +#endif + + if (!mediacontroller) { + return NS_ERROR_NO_INTERFACE; + } + + mediacontroller->Init(uri); + + mediacontroller.forget(aResult); + return NS_OK; +} + +} // namespace net +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/netwerk/base/src/StreamingProtocolService.h @@ -0,0 +1,33 @@ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef mozilla_net_StreamingProtocolControllerService_h +#define mozilla_net_StreamingProtocolControllerService_h + +#include "mozilla/StaticPtr.h" +#include "nsIStreamingProtocolService.h" +#include "nsIStreamingProtocolController.h" +#include "nsCOMPtr.h" +#include "nsIChannel.h" + +namespace mozilla { +namespace net { + +/** + * This class implements a service to help to create streaming protocol controller. + */ +class StreamingProtocolControllerService : public nsIStreamingProtocolControllerService +{ +public: + NS_DECL_ISUPPORTS + NS_DECL_NSISTREAMINGPROTOCOLCONTROLLERSERVICE + + StreamingProtocolControllerService() {}; + virtual ~StreamingProtocolControllerService() {}; + static already_AddRefed<StreamingProtocolControllerService> GetInstance(); +}; +} // namespace dom +} // namespace mozilla + +#endif //mozilla_net_StreamingProtocolControllerService_h
--- a/netwerk/base/src/moz.build +++ b/netwerk/base/src/moz.build @@ -69,16 +69,17 @@ CPP_SOURCES += [ 'nsSyncStreamListener.cpp', 'nsTemporaryFileInputStream.cpp', 'nsTransportUtils.cpp', 'nsUDPServerSocket.cpp', 'nsURIChecker.cpp', 'nsURLHelper.cpp', 'nsURLParsers.cpp', 'nsUnicharStreamLoader.cpp', + 'StreamingProtocolService.cpp', ] if CONFIG['MOZ_WIDGET_TOOLKIT'] == 'os2': CPP_SOURCES += [ 'nsURLHelperOS2.cpp', ] elif CONFIG['MOZ_WIDGET_TOOLKIT'] == 'windows': CPP_SOURCES += [
--- a/netwerk/build/Makefile.in +++ b/netwerk/build/Makefile.in @@ -13,16 +13,20 @@ SHARED_LIBRARY_LIBS = \ ../cache/$(LIB_PREFIX)nkcache_s.$(LIB_SUFFIX) \ ../cache2/$(LIB_PREFIX)nkcache2_s.$(LIB_SUFFIX) \ ../protocol/about/$(LIB_PREFIX)nkabout_s.$(LIB_SUFFIX) \ $(foreach d,$(filter-out about,$(NECKO_PROTOCOLS)), \ ../protocol/$(d)/$(LIB_PREFIX)nk$(d)_s.$(LIB_SUFFIX)) \ ../ipc/$(LIB_PREFIX)neckoipc_s.$(LIB_SUFFIX) \ $(NULL) +ifdef MOZ_RTSP +SHARED_LIBRARY_LIBS += ../protocol/rtsp/$(LIB_PREFIX)nkrtsp_s.$(LIB_SUFFIX) +endif + ifdef MOZ_SRTP SHARED_LIBRARY_LIBS += \ ../srtp/src/$(LIB_PREFIX)nksrtp_s.$(LIB_SUFFIX) \ $(NULL) endif ifdef MOZ_SCTP SHARED_LIBRARY_LIBS += \ @@ -96,10 +100,14 @@ endif ifdef NECKO_WIFI SHARED_LIBRARY_LIBS += \ ../wifi/$(LIB_PREFIX)neckowifi_s.$(LIB_SUFFIX) \ $(NULL) LOCAL_INCLUDES += -I$(srcdir)/../wifi endif +ifdef MOZ_RTSP + LOCAL_INCLUDES += -I$(srcdir)/../protocol/rtsp +endif + include $(topsrcdir)/config/rules.mk include $(topsrcdir)/ipc/chromium/chromium-config.mk
--- a/netwerk/build/nsNetCID.h +++ b/netwerk/build/nsNetCID.h @@ -666,16 +666,28 @@ { /* {dc01dbbb-a5bb-4cbb-82bb-085cce06c0bb} */ \ 0xdc01dbbb, \ 0xa5bb, \ 0x4cbb, \ {0x82, 0xbb, 0x08, 0x5c, 0xce, 0x06, 0xc0, 0xbb} \ } /****************************************************************************** + * netwerk/protocol/rtsp / classes + */ + +#define NS_RTSPPROTOCOLHANDLER_CID \ +{ /* {5bb4b980-7b10-11e2-b92a-0800200c9a66} */ \ + 0x5bb4b980, \ + 0x7b10, \ + 0x11e2, \ + {0xb9, 0x2a, 0x08, 0x00, 0x20, 0x0c, 0x9a, 0x66} \ +} + +/****************************************************************************** * netwerk/protocol/about/ classes */ #define NS_ABOUTPROTOCOLHANDLER_CID \ { /* 9e3b6c90-2f75-11d3-8cd0-0060b0fc14a3 */ \ 0x9e3b6c90, \ 0x2f75, \ 0x11d3, \
--- a/netwerk/build/nsNetModule.cpp +++ b/netwerk/build/nsNetModule.cpp @@ -312,16 +312,25 @@ type##Constructor(nsISupports *aOuter, R WEB_SOCKET_HANDLER_CONSTRUCTOR(WebSocketChannel, false) WEB_SOCKET_HANDLER_CONSTRUCTOR(WebSocketSSLChannel, true) #undef WEB_SOCKET_HANDLER_CONSTRUCTOR } // namespace mozilla::net } // namespace mozilla #endif +#ifdef MOZ_RTSP +#include "RtspHandler.h" +namespace mozilla { +namespace net { +NS_GENERIC_FACTORY_CONSTRUCTOR(RtspHandler) +} // namespace mozilla::net +} // namespace mozilla +#endif + /////////////////////////////////////////////////////////////////////////////// #include "nsURIChecker.h" NS_GENERIC_FACTORY_CONSTRUCTOR(nsURIChecker) /////////////////////////////////////////////////////////////////////////////// #include "nsURLParsers.h" @@ -781,16 +790,19 @@ NS_DEFINE_NAMED_CID(NS_VIEWSOURCEHANDLER #endif #ifdef NECKO_PROTOCOL_wyciwyg NS_DEFINE_NAMED_CID(NS_WYCIWYGPROTOCOLHANDLER_CID); #endif #ifdef NECKO_PROTOCOL_websocket NS_DEFINE_NAMED_CID(NS_WEBSOCKETPROTOCOLHANDLER_CID); NS_DEFINE_NAMED_CID(NS_WEBSOCKETSSLPROTOCOLHANDLER_CID); #endif +#ifdef MOZ_RTSP +NS_DEFINE_NAMED_CID(NS_RTSPPROTOCOLHANDLER_CID); +#endif #if defined(XP_WIN) NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); #elif defined(MOZ_WIDGET_COCOA) NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); #elif defined(MOZ_ENABLE_QTNETWORK) NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); #elif defined(MOZ_WIDGET_ANDROID) NS_DEFINE_NAMED_CID(NS_NETWORK_LINK_SERVICE_CID); @@ -918,16 +930,19 @@ static const mozilla::Module::CIDEntry k { &kNS_WYCIWYGPROTOCOLHANDLER_CID, false, nullptr, nsWyciwygProtocolHandlerConstructor }, #endif #ifdef NECKO_PROTOCOL_websocket { &kNS_WEBSOCKETPROTOCOLHANDLER_CID, false, nullptr, mozilla::net::WebSocketChannelConstructor }, { &kNS_WEBSOCKETSSLPROTOCOLHANDLER_CID, false, nullptr, mozilla::net::WebSocketSSLChannelConstructor }, #endif +#ifdef MOZ_RTSP + { &kNS_RTSPPROTOCOLHANDLER_CID, false, NULL, mozilla::net::RtspHandlerConstructor }, +#endif #if defined(XP_WIN) { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNotifyAddrListenerConstructor }, #elif defined(MOZ_WIDGET_COCOA) { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsNetworkLinkServiceConstructor }, #elif defined(MOZ_ENABLE_QTNETWORK) { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsQtNetworkLinkServiceConstructor }, #elif defined(MOZ_WIDGET_ANDROID) { &kNS_NETWORK_LINK_SERVICE_CID, false, nullptr, nsAndroidNetworkLinkServiceConstructor }, @@ -1058,16 +1073,19 @@ static const mozilla::Module::ContractID #endif #ifdef NECKO_PROTOCOL_wyciwyg { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "wyciwyg", &kNS_WYCIWYGPROTOCOLHANDLER_CID }, #endif #ifdef NECKO_PROTOCOL_websocket { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "ws", &kNS_WEBSOCKETPROTOCOLHANDLER_CID }, { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "wss", &kNS_WEBSOCKETSSLPROTOCOLHANDLER_CID }, #endif +#ifdef MOZ_RTSP + { NS_NETWORK_PROTOCOL_CONTRACTID_PREFIX "rtsp", &kNS_RTSPPROTOCOLHANDLER_CID }, +#endif #if defined(XP_WIN) { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID }, #elif defined(MOZ_WIDGET_COCOA) { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID }, #elif defined(MOZ_ENABLE_QTNETWORK) { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID }, #elif defined(MOZ_WIDGET_ANDROID) { NS_NETWORK_LINK_SERVICE_CONTRACTID, &kNS_NETWORK_LINK_SERVICE_CID },
--- a/netwerk/ipc/NeckoChild.cpp +++ b/netwerk/ipc/NeckoChild.cpp @@ -11,16 +11,19 @@ #include "mozilla/net/HttpChannelChild.h" #include "mozilla/net/CookieServiceChild.h" #include "mozilla/net/WyciwygChannelChild.h" #include "mozilla/net/FTPChannelChild.h" #include "mozilla/net/WebSocketChannelChild.h" #include "mozilla/net/RemoteOpenFileChild.h" #include "mozilla/dom/network/TCPSocketChild.h" #include "mozilla/dom/network/TCPServerSocketChild.h" +#ifdef MOZ_RTSP +#include "mozilla/net/RtspControllerChild.h" +#endif using mozilla::dom::TCPSocketChild; using mozilla::dom::TCPServerSocketChild; namespace mozilla { namespace net { PNeckoChild *gNeckoChild = nullptr; @@ -150,16 +153,33 @@ NeckoChild::AllocPWebSocketChild(PBrowse bool NeckoChild::DeallocPWebSocketChild(PWebSocketChild* child) { WebSocketChannelChild* p = static_cast<WebSocketChannelChild*>(child); p->ReleaseIPDLReference(); return true; } +PRtspControllerChild* +NeckoChild::AllocPRtspControllerChild() +{ + NS_NOTREACHED("AllocPRtspController should not be called"); + return nullptr; +} + +bool +NeckoChild::DeallocPRtspControllerChild(PRtspControllerChild* child) +{ +#ifdef MOZ_RTSP + RtspControllerChild* p = static_cast<RtspControllerChild*>(child); + p->ReleaseIPDLReference(); +#endif + return true; +} + PTCPSocketChild* NeckoChild::AllocPTCPSocketChild() { TCPSocketChild* p = new TCPSocketChild(); p->AddIPDLReference(); return p; }
--- a/netwerk/ipc/NeckoChild.h +++ b/netwerk/ipc/NeckoChild.h @@ -45,16 +45,18 @@ protected: virtual bool DeallocPTCPSocketChild(PTCPSocketChild*); virtual PTCPServerSocketChild* AllocPTCPServerSocketChild(const uint16_t& aLocalPort, const uint16_t& aBacklog, const nsString& aBinaryType); virtual bool DeallocPTCPServerSocketChild(PTCPServerSocketChild*); virtual PRemoteOpenFileChild* AllocPRemoteOpenFileChild(const URIParams&, const OptionalURIParams&); virtual bool DeallocPRemoteOpenFileChild(PRemoteOpenFileChild*); + virtual PRtspControllerChild* AllocPRtspControllerChild(); + virtual bool DeallocPRtspControllerChild(PRtspControllerChild*); }; /** * Reference to the PNecko Child protocol. * Null if this is not a content process. */ extern PNeckoChild *gNeckoChild;
--- a/netwerk/ipc/NeckoParent.cpp +++ b/netwerk/ipc/NeckoParent.cpp @@ -7,16 +7,19 @@ #include "nsHttp.h" #include "mozilla/net/NeckoParent.h" #include "mozilla/net/HttpChannelParent.h" #include "mozilla/net/CookieServiceParent.h" #include "mozilla/net/WyciwygChannelParent.h" #include "mozilla/net/FTPChannelParent.h" #include "mozilla/net/WebSocketChannelParent.h" +#ifdef MOZ_RTSP +#include "mozilla/net/RtspControllerParent.h" +#endif #include "mozilla/net/RemoteOpenFileParent.h" #include "mozilla/dom/ContentParent.h" #include "mozilla/dom/TabParent.h" #include "mozilla/dom/network/TCPSocketParent.h" #include "mozilla/dom/network/TCPServerSocketParent.h" #include "mozilla/ipc/URIUtils.h" #include "mozilla/LoadContext.h" #include "mozilla/AppProcessChecker.h" @@ -298,16 +301,38 @@ NeckoParent::AllocPWebSocketParent(PBrow bool NeckoParent::DeallocPWebSocketParent(PWebSocketParent* actor) { WebSocketChannelParent* p = static_cast<WebSocketChannelParent*>(actor); p->Release(); return true; } +PRtspControllerParent* +NeckoParent::AllocPRtspControllerParent() +{ +#ifdef MOZ_RTSP + RtspControllerParent* p = new RtspControllerParent(); + p->AddRef(); + return p; +#else + return nullptr; +#endif +} + +bool +NeckoParent::DeallocPRtspControllerParent(PRtspControllerParent* actor) +{ +#ifdef MOZ_RTSP + RtspControllerParent* p = static_cast<RtspControllerParent*>(actor); + p->Release(); +#endif + return true; +} + PTCPSocketParent* NeckoParent::AllocPTCPSocketParent() { TCPSocketParent* p = new TCPSocketParent(); p->AddIPDLReference(); return p; }
--- a/netwerk/ipc/NeckoParent.h +++ b/netwerk/ipc/NeckoParent.h @@ -120,16 +120,18 @@ protected: const uint16_t& flags); virtual bool RecvCancelHTMLDNSPrefetch(const nsString& hostname, const uint16_t& flags, const nsresult& reason); virtual mozilla::ipc::IProtocol* CloneProtocol(Channel* aChannel, mozilla::ipc::ProtocolCloneContext* aCtx) MOZ_OVERRIDE; + virtual PRtspControllerParent* AllocPRtspControllerParent(); + virtual bool DeallocPRtspControllerParent(PRtspControllerParent*); private: nsCString mCoreAppsBasePath; nsCString mWebAppsBasePath; }; } // namespace net } // namespace mozilla
--- a/netwerk/ipc/PNecko.ipdl +++ b/netwerk/ipc/PNecko.ipdl @@ -12,16 +12,17 @@ include protocol PBrowser; include protocol PWyciwygChannel; include protocol PFTPChannel; include protocol PWebSocket; include protocol PTCPSocket; include protocol PTCPServerSocket; include protocol PRemoteOpenFile; include protocol PBlob; //FIXME: bug #792908 +include protocol PRtspController; include URIParams; include InputStreamParams; include NeckoChannelParams; include "SerializedLoadContext.h"; using IPC::SerializedLoadContext; @@ -35,16 +36,17 @@ sync protocol PNecko manages PHttpChannel; manages PCookieService; manages PWyciwygChannel; manages PFTPChannel; manages PWebSocket; manages PTCPSocket; manages PTCPServerSocket; manages PRemoteOpenFile; + manages PRtspController; parent: __delete__(); PCookieService(); PHttpChannel(nullable PBrowser browser, SerializedLoadContext loadContext, HttpChannelCreationArgs args); @@ -53,16 +55,17 @@ parent: FTPChannelCreationArgs args); PWebSocket(PBrowser browser, SerializedLoadContext loadContext); PTCPServerSocket(uint16_t localPort, uint16_t backlog, nsString binaryType); PRemoteOpenFile(URIParams fileuri, OptionalURIParams appuri); HTMLDNSPrefetch(nsString hostname, uint16_t flags); CancelHTMLDNSPrefetch(nsString hostname, uint16_t flags, nsresult reason); + PRtspController(); both: PTCPSocket(); }; } // namespace net } // namespace mozilla
new file mode 100644 --- /dev/null +++ b/netwerk/ipc/PRtspController.ipdl @@ -0,0 +1,63 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 ft=cpp : */ + +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +include protocol PNecko; +include URIParams; + +namespace mozilla { +namespace net { + +/** + * Defined meta data format. + */ +union RtspMetaValue +{ + bool; + uint8_t; + uint32_t; + uint64_t; + nsCString; +}; + +/** + * Key-value pair. + */ +struct RtspMetadataParam +{ + nsCString name; + RtspMetaValue value; +}; + +async protocol PRtspController +{ + manager PNecko; + +parent: + AsyncOpen(URIParams aURI); + Play(); + Pause(); + Resume(); + Suspend(); + Seek(uint64_t offset); + Stop(); + __delete__(); + +child: + OnMediaDataAvailable(uint8_t index, + nsCString data, + uint32_t length, + uint32_t offset, + RtspMetadataParam[] meta); + OnConnected(uint8_t index, + RtspMetadataParam[] meta); + OnDisconnected(uint8_t index, + uint32_t reason); + AsyncOpenFailed(uint8_t reason); +}; + +} //namespace net +} //namespace mozilla
--- a/netwerk/ipc/moz.build +++ b/netwerk/ipc/moz.build @@ -30,16 +30,17 @@ CPP_SOURCES += [ 'RemoteOpenFileChild.cpp', 'RemoteOpenFileParent.cpp', ] IPDL_SOURCES = [ 'NeckoChannelParams.ipdlh', 'PNecko.ipdl', 'PRemoteOpenFile.ipdl', + 'PRtspController.ipdl', ] FAIL_ON_WARNINGS = True LIBXUL_LIBRARY = True LIBRARY_NAME = 'neckoipc_s'
--- a/netwerk/protocol/moz.build +++ b/netwerk/protocol/moz.build @@ -12,8 +12,14 @@ PARALLEL_DIRS += [ 'file', 'ftp', 'http', 'res', 'viewsource', 'websocket', 'wyciwyg', ] + +if CONFIG['MOZ_RTSP']: + PARALLEL_DIRS += [ + 'rtsp', + ] +
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/Makefile.in @@ -0,0 +1,25 @@ +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +DEPTH = @DEPTH@ +topsrcdir = @top_srcdir@ +srcdir = @srcdir@ + +include $(DEPTH)/config/autoconf.mk + +LOCAL_INCLUDES = \ + -I$(srcdir)/../../base/src \ + -I$(topsrcdir)/content/base/src \ + -I$(topsrcdir)/content/events/src \ + -I$(topsrcdir)/xpcom/ds \ + -I$(srcdir) \ + -I$(srcdir)/rtsp \ + -I$(srcdir)/controller \ + -I$(ANDROID_SOURCE)/frameworks/base/media/libstagefright/mpeg2ts \ + $(NULL) + +include $(topsrcdir)/config/rules.mk +include $(topsrcdir)/ipc/chromium/chromium-config.mk + +DEFINES += -DIMPL_NS_NET -Wno-multichar -DFORCE_PR_LOG
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/RtspChannel.cpp @@ -0,0 +1,62 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RtspChannel.h" +#include "nsIURI.h" +#include "nsAutoPtr.h" +#include "nsStandardURL.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS_INHERITED1(RtspChannel, + nsBaseChannel, + nsIChannel) + +//----------------------------------------------------------------------------- +// RtspChannel::nsIChannel +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +RtspChannel::AsyncOpen(nsIStreamListener *aListener, nsISupports *aContext) +{ + MOZ_ASSERT(aListener); + + nsCOMPtr<nsIURI> uri = nsBaseChannel::URI(); + NS_ENSURE_TRUE(uri, NS_ERROR_ILLEGAL_VALUE); + + nsAutoCString uriSpec; + uri->GetSpec(uriSpec); + + mListener = aListener; + mListenerContext = aContext; + + // Call OnStartRequest directly. mListener is expected to create/load an + // RtspMediaResource which will create an RtspMediaController. This controller + // manages the control and data streams to and from the network. + mListener->OnStartRequest(this, aContext); + return NS_OK; +} + +NS_IMETHODIMP +RtspChannel::GetContentType(nsACString& aContentType) +{ + aContentType.AssignLiteral("RTSP"); + return NS_OK; +} + +NS_IMETHODIMP +RtspChannel::Init(nsIURI* aUri) +{ + MOZ_ASSERT(aUri); + + nsBaseChannel::Init(); + nsBaseChannel::SetURI(aUri); + return NS_OK; +} + +} // namespace mozilla::net +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/RtspChannel.h @@ -0,0 +1,76 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RtspChannel_h +#define RtspChannel_h + +#include "nsBaseChannel.h" + +namespace mozilla { +namespace net { + +//----------------------------------------------------------------------------- +// RtspChannel is a dummy channel used to aid MediaResource creation in +// HTMLMediaElement. Actual network control and data flows are managed by an +// RtspController object created and owned by RtspMediaResource. +// Therefore, when RtspChannel::AsyncOpen is called, mListener->OnStartRequest +// will be called immediately. It is expected that an RtspMediaResource object +// will be created in that calling context or after; the RtspController object +// will be created internally by RtspMediaResource." + +class RtspChannel : public nsBaseChannel + , public nsIStreamListener +{ +public: + NS_DECL_ISUPPORTS + + RtspChannel() { } + + ~RtspChannel() { } + + // Overrides nsBaseChannel::AsyncOpen and call listener's OnStartRequest immediately. + NS_IMETHOD AsyncOpen(nsIStreamListener *listener, + nsISupports *aContext) MOZ_OVERRIDE MOZ_FINAL; + // Set Rtsp URL. + NS_IMETHOD Init(nsIURI* uri); + // Overrides nsBaseChannel::GetContentType, return streaming protocol type "RTSP". + NS_IMETHOD GetContentType(nsACString & aContentType) MOZ_OVERRIDE MOZ_FINAL; + + NS_IMETHOD OpenContentStream(bool aAsync, + nsIInputStream **aStream, + nsIChannel **aChannel) MOZ_OVERRIDE MOZ_FINAL + { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // nsIRequestObserver + NS_IMETHOD OnStartRequest(nsIRequest *aRequest, + nsISupports *aContext) MOZ_OVERRIDE MOZ_FINAL + { + return NS_ERROR_NOT_IMPLEMENTED; + } + + NS_IMETHOD OnStopRequest(nsIRequest *aRequest, + nsISupports *aContext, + nsresult aStatusCode) MOZ_OVERRIDE MOZ_FINAL + { + return NS_ERROR_NOT_IMPLEMENTED; + } + + // nsIStreamListener + NS_IMETHOD OnDataAvailable(nsIRequest *aRequest, + nsISupports *aContext, + nsIInputStream *aInputStream, + uint64_t aOffset, + uint32_t aCount) MOZ_OVERRIDE MOZ_FINAL + { + return NS_ERROR_NOT_IMPLEMENTED; + } +}; + +} +} // namespace mozilla::net +#endif // RtspChannel_h
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/RtspHandler.cpp @@ -0,0 +1,95 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RtspChannel.h" +#include "RtspHandler.h" +#include "nsILoadGroup.h" +#include "nsIInterfaceRequestor.h" +#include "nsIURI.h" +#include "nsAutoPtr.h" +#include "nsStandardURL.h" +#include "mozilla/net/NeckoChild.h" + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS1(RtspHandler, nsIProtocolHandler) + +//----------------------------------------------------------------------------- +// RtspHandler::nsIProtocolHandler +//----------------------------------------------------------------------------- + +NS_IMETHODIMP +RtspHandler::GetScheme(nsACString &aScheme) +{ + aScheme.AssignLiteral("rtsp"); + return NS_OK; +} + +NS_IMETHODIMP +RtspHandler::GetDefaultPort(int32_t *aDefaultPort) +{ + *aDefaultPort = kDefaultRtspPort; + return NS_OK; +} + +NS_IMETHODIMP +RtspHandler::GetProtocolFlags(uint32_t *aProtocolFlags) +{ + *aProtocolFlags = URI_NORELATIVE | URI_NOAUTH | URI_INHERITS_SECURITY_CONTEXT | + URI_LOADABLE_BY_ANYONE | URI_NON_PERSISTABLE | URI_SYNC_LOAD_IS_OK; + + return NS_OK; +} + +NS_IMETHODIMP +RtspHandler::NewURI(const nsACString & aSpec, + const char *aOriginCharset, + nsIURI *aBaseURI, nsIURI **aResult) +{ + int32_t port; + + nsresult rv = GetDefaultPort(&port); + NS_ENSURE_SUCCESS(rv, rv); + + nsRefPtr<nsStandardURL> url = new nsStandardURL(); + rv = url->Init(nsIStandardURL::URLTYPE_AUTHORITY, port, aSpec, + aOriginCharset, aBaseURI); + NS_ENSURE_SUCCESS(rv, rv); + + url.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +RtspHandler::NewChannel(nsIURI *aURI, nsIChannel **aResult) +{ + bool isRtsp = false; + nsRefPtr<RtspChannel> rtspChannel; + + nsresult rv = aURI->SchemeIs("rtsp", &isRtsp); + NS_ENSURE_SUCCESS(rv, rv); + NS_ENSURE_TRUE(isRtsp, NS_ERROR_UNEXPECTED); + + rtspChannel = new RtspChannel(); + + rv = rtspChannel->Init(aURI); + NS_ENSURE_SUCCESS(rv, rv); + + rtspChannel.forget(aResult); + return NS_OK; +} + +NS_IMETHODIMP +RtspHandler::AllowPort(int32_t port, const char *scheme, bool *aResult) +{ + // Do not override any blacklisted ports. + *aResult = false; + return NS_OK; +} + +} // namespace net +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/RtspHandler.h @@ -0,0 +1,32 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RtspHandler_h +#define RtspHandler_h + +#include "nsIProtocolHandler.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +namespace mozilla { +namespace net { + + +class RtspHandler : public nsIProtocolHandler +{ + public: + NS_DECL_ISUPPORTS + NS_DECL_NSIPROTOCOLHANDLER + + RtspHandler() { } + ~RtspHandler() { } + const static int32_t kDefaultRtspPort = 554; +}; + +} // namespace net +} // namespace mozilla + +#endif // RtspHandler_h
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/controller/RtspController.cpp @@ -0,0 +1,371 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RtspController.h" +#include "RtspMetaData.h" +#include "nsIURI.h" +#include "nsICryptoHash.h" +#include "nsIRunnable.h" +#include "nsIPrefBranch.h" +#include "nsIPrefService.h" +#include "nsICancelable.h" +#include "nsIStreamConverterService.h" +#include "nsIIOService2.h" +#include "nsIProtocolProxyService.h" +#include "nsIProxyInfo.h" +#include "nsIProxiedChannel.h" + +#include "nsAutoPtr.h" +#include "nsStandardURL.h" +#include "nsNetCID.h" +#include "nsServiceManagerUtils.h" +#include "nsXPIDLString.h" +#include "nsCRT.h" +#include "nsThreadUtils.h" +#include "nsError.h" +#include "nsStringStream.h" +#include "nsAlgorithm.h" +#include "nsProxyRelease.h" +#include "nsNetUtil.h" +#include "mozilla/Attributes.h" +#include "TimeStamp.h" +#include "mozilla/Telemetry.h" +#include "prlog.h" + +#include "plbase64.h" +#include "prmem.h" +#include "prnetdb.h" +#include "prbit.h" +#include "zlib.h" +#include <algorithm> +#include "nsDebug.h" + +extern PRLogModuleInfo* gRtspLog; +#undef LOG +#define LOG(args) PR_LOG(gRtspLog, PR_LOG_DEBUG, args) + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS1(RtspController, + nsIStreamingProtocolController) + +RtspController::RtspController(nsIChannel *channel) + : mState(INIT) +{ + LOG(("RtspController::RtspController()")); +} + +RtspController::~RtspController() +{ + LOG(("RtspController::~RtspController()")); + if (mRtspSource.get()) { + mRtspSource.clear(); + } +} + +NS_IMETHODIMP +RtspController::GetTrackMetaData(uint8_t index, + nsIStreamingProtocolMetaData * *_retval) +{ + LOG(("RtspController::GetTrackMetaData()")); + return NS_OK; +} + +NS_IMETHODIMP +RtspController::Play(void) +{ + LOG(("RtspController::Play()")); + if (!mRtspSource.get()) { + MOZ_ASSERT(mRtspSource.get(), "mRtspSource should not be null!"); + return NS_ERROR_NOT_INITIALIZED; + } + + if (mState != CONNECTED) { + return NS_ERROR_UNEXPECTED; + } + + mRtspSource->play(); + return NS_OK; +} + +NS_IMETHODIMP +RtspController::Pause(void) +{ + LOG(("RtspController::Pause()")); + if (!mRtspSource.get()) { + MOZ_ASSERT(mRtspSource.get(), "mRtspSource should not be null!"); + return NS_ERROR_NOT_INITIALIZED; + } + + if (mState != CONNECTED) { + return NS_ERROR_UNEXPECTED; + } + + mRtspSource->pause(); + return NS_OK; +} + +NS_IMETHODIMP +RtspController::Resume(void) +{ + LOG(("RtspController::Resume()")); + if (!mRtspSource.get()) { + MOZ_ASSERT(mRtspSource.get(), "mRtspSource should not be null!"); + return NS_ERROR_NOT_INITIALIZED; + } + + if (mState != CONNECTED) { + return NS_ERROR_UNEXPECTED; + } + + mRtspSource->play(); + return NS_OK; +} + +NS_IMETHODIMP +RtspController::Suspend(void) +{ + LOG(("RtspController::Suspend()")); + if (!mRtspSource.get()) { + MOZ_ASSERT(mRtspSource.get(), "mRtspSource should not be null!"); + return NS_ERROR_NOT_INITIALIZED; + } + + if (mState != CONNECTED) { + return NS_ERROR_UNEXPECTED; + } + + mRtspSource->pause(); + return NS_OK; +} + +NS_IMETHODIMP +RtspController::Seek(uint64_t seekTimeUs) +{ + LOG(("RtspController::Seek() %llu", seekTimeUs)); + if (!mRtspSource.get()) { + MOZ_ASSERT(mRtspSource.get(), "mRtspSource should not be null!"); + return NS_ERROR_NOT_INITIALIZED; + } + + if (mState != CONNECTED) { + return NS_ERROR_UNEXPECTED; + } + + mRtspSource->seek(seekTimeUs); + return NS_OK; +} + +NS_IMETHODIMP +RtspController::Stop() +{ + LOG(("RtspController::Stop()")); + mState = INIT; + if (!mRtspSource.get()) { + MOZ_ASSERT(mRtspSource.get(), "mRtspSource should not be null!"); + return NS_ERROR_NOT_INITIALIZED; + } + + mRtspSource->stop(); + return NS_OK; +} + +NS_IMETHODIMP +RtspController::GetTotalTracks(uint8_t *aTracks) +{ + LOG(("RtspController::GetTotalTracks()")); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +RtspController::AsyncOpen(nsIStreamingProtocolListener *aListener) +{ + if (!aListener) { + LOG(("RtspController::AsyncOpen() illegal listener")); + return NS_ERROR_NOT_INITIALIZED; + } + + mListener = aListener; + + if (!mURI) { + LOG(("RtspController::AsyncOpen() illegal URI")); + return NS_ERROR_ILLEGAL_VALUE; + } + + nsAutoCString uriSpec; + mURI->GetSpec(uriSpec); + LOG(("RtspController AsyncOpen uri=%s", uriSpec.get())); + + if (!mRtspSource.get()) { + mRtspSource = new android::RTSPSource(this, uriSpec.get(), false, 0); + } + // Connect to Rtsp Server. + mRtspSource->start(); + + return NS_OK; +} + +class SendMediaDataTask : public nsRunnable +{ +public: + SendMediaDataTask(nsIStreamingProtocolListener *listener, + uint8_t index, + const nsACString & data, + uint32_t length, + uint32_t offset, + nsIStreamingProtocolMetaData *meta) + : mIndex(index) + , mLength(length) + , mOffset(offset) + , mMetaData(meta) + , mListener(listener) + { + mData.Assign(data); + } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + mListener->OnMediaDataAvailable(mIndex, mData, mLength, + mOffset, mMetaData); + return NS_OK; + } + +private: + uint8_t mIndex; + nsCString mData; + uint32_t mLength; + uint32_t mOffset; + nsRefPtr<nsIStreamingProtocolMetaData> mMetaData; + nsCOMPtr<nsIStreamingProtocolListener> mListener; +}; + +NS_IMETHODIMP +RtspController::OnMediaDataAvailable(uint8_t index, + const nsACString & data, + uint32_t length, + uint32_t offset, + nsIStreamingProtocolMetaData *meta) +{ + if (mListener && mState == CONNECTED) { + nsRefPtr<SendMediaDataTask> task = + new SendMediaDataTask(mListener, index, data, length, offset, meta); + return NS_DispatchToMainThread(task); + } + return NS_ERROR_NOT_AVAILABLE; +} + +class SendOnConnectedTask : public nsRunnable +{ +public: + SendOnConnectedTask(nsIStreamingProtocolListener *listener, + uint8_t index, + nsIStreamingProtocolMetaData *meta) + : mListener(listener) + , mIndex(index) + , mMetaData(meta) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + mListener->OnConnected(mIndex, mMetaData); + return NS_OK; + } + +private: + nsCOMPtr<nsIStreamingProtocolListener> mListener; + uint8_t mIndex; + nsRefPtr<nsIStreamingProtocolMetaData> mMetaData; +}; + + +NS_IMETHODIMP +RtspController::OnConnected(uint8_t index, + nsIStreamingProtocolMetaData *meta) +{ + LOG(("RtspController::OnConnected()")); + mState = CONNECTED; + if (mListener) { + nsRefPtr<SendOnConnectedTask> task = + new SendOnConnectedTask(mListener, index, meta); + return NS_DispatchToMainThread(task); + } + return NS_ERROR_NOT_AVAILABLE; +} + +class SendOnDisconnectedTask : public nsRunnable +{ +public: + SendOnDisconnectedTask(nsIStreamingProtocolListener *listener, + uint8_t index, + uint32_t reason) + : mListener(listener) + , mIndex(index) + , mReason(reason) + { } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + mListener->OnDisconnected(mIndex, mReason); + return NS_OK; + } + +private: + nsCOMPtr<nsIStreamingProtocolListener> mListener; + uint8_t mIndex; + uint32_t mReason; +}; + +NS_IMETHODIMP +RtspController::OnDisconnected(uint8_t index, + uint32_t reason) +{ + LOG(("RtspController::OnDisconnected()")); + mState = DISCONNECTED; + if (mListener) { + nsRefPtr<SendOnDisconnectedTask> task = + new SendOnDisconnectedTask(mListener, index, reason); + return NS_DispatchToMainThread(task); + } + return NS_ERROR_NOT_AVAILABLE; +} + +NS_IMETHODIMP +RtspController::Init(nsIURI *aURI) +{ + nsresult rv; + + if (!aURI) { + LOG(("RtspController::Init() - invalid URI")); + return NS_ERROR_NOT_INITIALIZED; + } + + nsAutoCString host; + int32_t port = -1; + + rv = aURI->GetAsciiHost(host); + if (NS_FAILED(rv)) return rv; + + // Reject the URL if it doesn't specify a host + if (host.IsEmpty()) + return NS_ERROR_MALFORMED_URI; + + rv = aURI->GetPort(&port); + if (NS_FAILED(rv)) return rv; + + rv = aURI->GetAsciiSpec(mSpec); + if (NS_FAILED(rv)) return rv; + + mURI = aURI; + + return NS_OK; +} + +} // namespace mozilla::net +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/controller/RtspController.h @@ -0,0 +1,52 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RtspController_h +#define RtspController_h + +#include "nsIStreamingProtocolController.h" +#include "nsIChannel.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "RTSPSource.h" + +namespace mozilla { +namespace net { + +class RtspController : public nsIStreamingProtocolController + , public nsIStreamingProtocolListener +{ +public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTREAMINGPROTOCOLCONTROLLER + NS_DECL_NSISTREAMINGPROTOCOLLISTENER + + RtspController(nsIChannel *channel); + ~RtspController(); + +private: + enum State { + INIT, + CONNECTED, + DISCONNECTED + }; + + // RTSP URL refer to a stream or an aggregate of streams. + nsCOMPtr<nsIURI> mURI; + // The nsIStreamingProtocolListener implementation. + nsCOMPtr<nsIStreamingProtocolListener> mListener; + // ASCII encoded URL spec. + nsCString mSpec; + // Indicate the connection state between the + // media streaming server and the Rtsp client. + State mState; + // Rtsp Streaming source. + android::sp<android::RTSPSource> mRtspSource; +}; + +} +} // namespace mozilla::net +#endif
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/controller/RtspControllerChild.cpp @@ -0,0 +1,466 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RtspControllerChild.h" +#include "RtspMetaData.h" +#include "mozilla/dom/TabChild.h" +#include "mozilla/net/NeckoChild.h" +#include "nsITabChild.h" +#include "nsILoadContext.h" +#include "nsNetUtil.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "nsStringStream.h" +#include "prlog.h" + +PRLogModuleInfo* gRtspChildLog = nullptr; +#undef LOG +#define LOG(args) PR_LOG(gRtspChildLog, PR_LOG_DEBUG, args) + +const uint32_t kRtspTotalTracks = 2; +using namespace mozilla::ipc; + +namespace mozilla { +namespace net { + +NS_IMPL_ADDREF(RtspControllerChild) + +NS_IMETHODIMP_(nsrefcnt) RtspControllerChild::Release() +{ + NS_PRECONDITION(0 != mRefCnt, "dup release"); + // Enable this to find non-threadsafe destructors: + // NS_ASSERT_OWNINGTHREAD(RtspControllerChild); + --mRefCnt; + NS_LOG_RELEASE(this, mRefCnt, "RtspControllerChild"); + + if (mRefCnt == 1 && mIPCOpen) { + Send__delete__(this); + return mRefCnt; + } + + if (mRefCnt == 0) { + mRefCnt = 1; /* stabilize */ + delete this; + return 0; + } + return mRefCnt; +} + +NS_INTERFACE_MAP_BEGIN(RtspControllerChild) + NS_INTERFACE_MAP_ENTRY(nsIStreamingProtocolController) + NS_INTERFACE_MAP_ENTRY(nsIStreamingProtocolListener) + NS_INTERFACE_MAP_ENTRY_AMBIGUOUS(nsISupports, nsIStreamingProtocolController) +NS_INTERFACE_MAP_END + +RtspControllerChild::RtspControllerChild(nsIChannel *channel) + : mIPCOpen(false) + , mChannel(channel) + , mTotalTracks(0) + , mSuspendCount(0) +{ +#if defined(PR_LOGGING) + if (!gRtspChildLog) + gRtspChildLog = PR_NewLogModule("nsRtspChild"); +#endif + AddIPDLReference(); + gNeckoChild->SendPRtspControllerConstructor(this); +} + +RtspControllerChild::~RtspControllerChild() +{ + LOG(("RtspControllerChild::~RtspControllerChild()")); +} + +bool +RtspControllerChild::RecvOnMediaDataAvailable( + const uint8_t& index, + const nsCString& data, + const uint32_t& length, + const uint32_t& offset, + const InfallibleTArray<RtspMetadataParam>& metaArray) +{ + nsRefPtr<RtspMetaData> meta = new RtspMetaData(); + nsresult rv = meta->DeserializeRtspMetaData(metaArray); + NS_ENSURE_SUCCESS(rv, false); + + if (mListener) { + mListener->OnMediaDataAvailable(index, data, length, offset, meta.get()); + } + return true; +} + +void +RtspControllerChild::AddMetaData( + already_AddRefed<nsIStreamingProtocolMetaData> meta) +{ + mMetaArray.AppendElement(meta); +} + +int +RtspControllerChild::GetMetaDataLength() +{ + return mMetaArray.Length(); +} + +bool +RtspControllerChild::RecvOnConnected( + const uint8_t& index, + const InfallibleTArray<RtspMetadataParam>& metaArray) +{ + uint32_t tracks; + + // Deserialize meta data. + nsRefPtr<RtspMetaData> meta = new RtspMetaData(); + nsresult rv = meta->DeserializeRtspMetaData(metaArray); + NS_ENSURE_SUCCESS(rv, false); + meta->GetTotalTracks(&tracks); + if (tracks <= 0) { + LOG(("RtspControllerChild::RecvOnConnected invalid tracks %d", tracks)); + // Set the default value. + tracks = kRtspTotalTracks; + } + mTotalTracks = tracks; + AddMetaData(meta.forget()); + + // Notify the listener when meta data of tracks are available. + if ((index + 1) == tracks) { + // The controller provide |GetTrackMetaData| method for his client. + if (mListener) { + mListener->OnConnected(index, nullptr); + } + } + return true; +} + +bool +RtspControllerChild::RecvOnDisconnected( + const uint8_t& index, + const uint32_t& reason) +{ + LOG(("RtspControllerChild::RecvOnDisconnected %d %d", index, reason)); + if (mListener) { + mListener->OnDisconnected(index, reason); + } + return true; +} + +bool +RtspControllerChild::RecvAsyncOpenFailed(const uint8_t& reason) +{ + LOG(("RtspControllerChild::RecvAsyncOpenFailed %d", reason)); + if (mListener) { + mListener->OnDisconnected(0, NS_ERROR_CONNECTION_REFUSED); + } + return true; +} + +void +RtspControllerChild::AddIPDLReference() +{ + NS_ABORT_IF_FALSE(!mIPCOpen, + "Attempt to retain more than one IPDL reference"); + mIPCOpen = true; + AddRef(); +} + +void +RtspControllerChild::ReleaseIPDLReference() +{ + NS_ABORT_IF_FALSE(mIPCOpen, "Attempt to release nonexistent IPDL reference"); + mIPCOpen = false; + Release(); +} + +NS_IMETHODIMP +RtspControllerChild::GetTrackMetaData( + uint8_t index, + nsIStreamingProtocolMetaData **result) +{ + if (GetMetaDataLength() <= 0 || index >= GetMetaDataLength()) { + LOG(("RtspControllerChild:: meta data is not available")); + return NS_ERROR_NOT_INITIALIZED; + } + LOG(("RtspControllerChild::GetTrackMetaData() %d", index)); + NS_IF_ADDREF(*result = mMetaArray[index]); + return NS_OK; +} + +enum IPCEvent +{ + SendNoneEvent = 0, + SendPlayEvent, + SendPauseEvent, + SendSeekEvent, + SendResumeEvent, + SendSuspendEvent, + SendStopEvent +}; + +class SendIPCEvent : public nsRunnable +{ +public: + SendIPCEvent(RtspControllerChild *aController, IPCEvent aEvent) + : mController(aController) + , mEvent(aEvent) + , mSeekTime(0) + { + } + + SendIPCEvent(RtspControllerChild *aController, + IPCEvent aEvent, + uint64_t aSeekTime) + : mController(aController) + , mEvent(aEvent) + , mSeekTime(aSeekTime) + { + } + + NS_IMETHOD Run() + { + MOZ_ASSERT(NS_IsMainThread()); + + if (mEvent == SendPlayEvent) { + mController->SendPlay(); + } else if (mEvent == SendPauseEvent) { + mController->SendPause(); + } else if (mEvent == SendSeekEvent) { + mController->SendSeek(mSeekTime); + } else if (mEvent == SendResumeEvent) { + mController->SendResume(); + } else if (mEvent == SendSuspendEvent) { + mController->SendSuspend(); + } else if (mEvent == SendStopEvent) { + mController->SendStop(); + } else { + LOG(("RtspControllerChild::SendIPCEvent")); + } + return NS_OK; + } +private: + nsRefPtr<RtspControllerChild> mController; + IPCEvent mEvent; + uint64_t mSeekTime; +}; + +NS_IMETHODIMP +RtspControllerChild::Play(void) +{ + LOG(("RtspControllerChild::Play()")); + NS_ENSURE_SUCCESS(mIPCOpen, NS_ERROR_FAILURE); + + if (NS_IsMainThread()) { + if (!SendPlay()) + return NS_ERROR_FAILURE; + } else { + nsresult rv = NS_DispatchToMainThread( + new SendIPCEvent(this, SendPlayEvent)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +RtspControllerChild::Pause(void) +{ + LOG(("RtspControllerChild::Pause()")); + NS_ENSURE_SUCCESS(mIPCOpen, NS_ERROR_FAILURE); + + if (NS_IsMainThread()) { + if (!SendPause()) + return NS_ERROR_FAILURE; + } else { + nsresult rv = NS_DispatchToMainThread( + new SendIPCEvent(this, SendPauseEvent)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +RtspControllerChild::Resume(void) +{ + LOG(("RtspControllerChild::Resume()")); + NS_ENSURE_SUCCESS(mIPCOpen, NS_ERROR_FAILURE); + NS_ENSURE_TRUE(mSuspendCount > 0, NS_ERROR_UNEXPECTED); + + if (!--mSuspendCount) { + if (NS_IsMainThread()) { + if (!SendResume()) + return NS_ERROR_FAILURE; + } else { + nsresult rv = NS_DispatchToMainThread( + new SendIPCEvent(this, SendResumeEvent)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +RtspControllerChild::Suspend(void) +{ + LOG(("RtspControllerChild::Suspend()")); + NS_ENSURE_SUCCESS(mIPCOpen, NS_ERROR_FAILURE); + + if (!mSuspendCount++) { + if (NS_IsMainThread()) { + if (!SendSuspend()) + return NS_ERROR_FAILURE; + } else { + nsresult rv = NS_DispatchToMainThread( + new SendIPCEvent(this, SendSuspendEvent)); + NS_ENSURE_SUCCESS(rv, rv); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +RtspControllerChild::Seek(uint64_t seekTimeUs) +{ + LOG(("RtspControllerChild::Seek() %llu", seekTimeUs)); + NS_ENSURE_SUCCESS(mIPCOpen, NS_ERROR_FAILURE); + + if (NS_IsMainThread()) { + if (!SendSeek(seekTimeUs)) + return NS_ERROR_FAILURE; + } else { + nsresult rv = NS_DispatchToMainThread( + new SendIPCEvent(this, SendSeekEvent, seekTimeUs)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +RtspControllerChild::Stop() +{ + LOG(("RtspControllerChild::Stop()")); + NS_ENSURE_SUCCESS(mIPCOpen, NS_ERROR_FAILURE); + + if (NS_IsMainThread()) { + if (!SendStop()) + return NS_ERROR_FAILURE; + } else { + nsresult rv = NS_DispatchToMainThread( + new SendIPCEvent(this, SendStopEvent)); + NS_ENSURE_SUCCESS(rv, rv); + } + + return NS_OK; +} + +NS_IMETHODIMP +RtspControllerChild::GetTotalTracks(uint8_t *aTracks) +{ + NS_ENSURE_ARG_POINTER(aTracks); + *aTracks = kRtspTotalTracks; + if (mTotalTracks) { + *aTracks = mTotalTracks; + } + LOG(("RtspControllerChild::GetTracks() %d", *aTracks)); + return NS_OK; +} + +NS_IMETHODIMP +RtspControllerChild::OnMediaDataAvailable(uint8_t index, + const nsACString & data, + uint32_t length, + uint32_t offset, + nsIStreamingProtocolMetaData *meta) +{ + LOG(("RtspControllerChild::OnMediaDataAvailable()")); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +RtspControllerChild::OnConnected(uint8_t index, + nsIStreamingProtocolMetaData *meta) + +{ + LOG(("RtspControllerChild::OnConnected()")); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +RtspControllerChild::OnDisconnected(uint8_t index, + uint32_t reason) +{ + LOG(("RtspControllerChild::OnDisconnected()")); + return NS_ERROR_NOT_IMPLEMENTED; +} + +NS_IMETHODIMP +RtspControllerChild::Init(nsIURI *aURI) +{ + nsresult rv; + + if (!aURI) { + LOG(("RtspControllerChild::Init() - invalid URI")); + return NS_ERROR_NOT_INITIALIZED; + } + + nsAutoCString host; + int32_t port = -1; + + rv = aURI->GetAsciiHost(host); + if (NS_FAILED(rv)) return rv; + + // Reject the URL if it doesn't specify a host + if (host.IsEmpty()) + return NS_ERROR_MALFORMED_URI; + + rv = aURI->GetPort(&port); + if (NS_FAILED(rv)) return rv; + + rv = aURI->GetAsciiSpec(mSpec); + if (NS_FAILED(rv)) return rv; + + if (!strncmp(mSpec.get(), "rtsp:", 5) == 0) + return NS_ERROR_UNEXPECTED; + + mURI = aURI; + + return NS_OK; +} + +NS_IMETHODIMP +RtspControllerChild::AsyncOpen(nsIStreamingProtocolListener *aListener) +{ + LOG(("RtspControllerChild::AsyncOpen()")); + if (!aListener) { + LOG(("RtspControllerChild::AsyncOpen() - invalid listener")); + return NS_ERROR_NOT_INITIALIZED; + } + mListener = aListener; + + if (!mChannel) { + LOG(("RtspControllerChild::AsyncOpen() - invalid URI")); + return NS_ERROR_NOT_INITIALIZED; + } + + nsCOMPtr<nsIURI> uri; + URIParams uriParams; + mChannel->GetURI(getter_AddRefs(uri)); + if (!uri) { + LOG(("RtspControllerChild::AsyncOpen() - invalid URI")); + return NS_ERROR_NOT_INITIALIZED; + } + SerializeURI(uri, uriParams); + + if (!mIPCOpen || !SendAsyncOpen(uriParams)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +} // namespace net +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/controller/RtspControllerChild.h @@ -0,0 +1,71 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RtspControllerChild_h +#define RtspControllerChild_h + +#include "mozilla/net/PRtspControllerChild.h" +#include "nsIStreamingProtocolController.h" +#include "nsIChannel.h" +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsTArray.h" + +namespace mozilla { +namespace net { + +class RtspControllerChild : public nsIStreamingProtocolController + , public nsIStreamingProtocolListener + , public PRtspControllerChild +{ + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTREAMINGPROTOCOLCONTROLLER + NS_DECL_NSISTREAMINGPROTOCOLLISTENER + + RtspControllerChild(nsIChannel *channel); + ~RtspControllerChild(); + + bool RecvOnConnected(const uint8_t& index, + const InfallibleTArray<RtspMetadataParam>& meta); + + bool RecvOnMediaDataAvailable( + const uint8_t& index, + const nsCString& data, + const uint32_t& length, + const uint32_t& offset, + const InfallibleTArray<RtspMetadataParam>& meta); + + bool RecvOnDisconnected(const uint8_t& index, + const uint32_t& reason); + + bool RecvAsyncOpenFailed(const uint8_t& reason); + void AddIPDLReference(); + void ReleaseIPDLReference(); + void AddMetaData(already_AddRefed<nsIStreamingProtocolMetaData> meta); + int GetMetaDataLength(); + + private: + bool mIPCOpen; + // Dummy channel used to aid MediaResource creation in HTMLMediaElement. + nsCOMPtr<nsIChannel> mChannel; + // The nsIStreamingProtocolListener implementation. + nsCOMPtr<nsIStreamingProtocolListener> mListener; + // RTSP URL refer to a stream or an aggregate of streams. + nsCOMPtr<nsIURI> mURI; + // Array refer to metadata of the media stream. + nsTArray<nsCOMPtr<nsIStreamingProtocolMetaData>> mMetaArray; + // ASCII encoded URL spec + nsCString mSpec; + // The total tracks for the given media stream session. + uint32_t mTotalTracks; + // Current suspension depth for this channel object + uint32_t mSuspendCount; +}; +} // namespace net +} // namespace mozilla + +#endif // RtspControllerChild_h
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/controller/RtspControllerParent.cpp @@ -0,0 +1,264 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RtspControllerParent.h" +#include "RtspController.h" +#include "nsIAuthPromptProvider.h" +#include "mozilla/ipc/InputStreamUtils.h" +#include "mozilla/ipc/URIUtils.h" +#include "nsNetUtil.h" +#include "prlog.h" + +#include <sys/types.h> +#include <sys/socket.h> + +PRLogModuleInfo* gRtspLog; +#undef LOG +#define LOG(args) PR_LOG(gRtspLog, PR_LOG_DEBUG, args) + +using namespace mozilla::ipc; + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS2(RtspControllerParent, + nsIInterfaceRequestor, + nsIStreamingProtocolListener) + +RtspControllerParent::RtspControllerParent() + : mIPCOpen(true) +{ +#if defined(PR_LOGGING) + if (!gRtspLog) + gRtspLog = PR_NewLogModule("nsRtsp"); +#endif +} + +RtspControllerParent::~RtspControllerParent() +{ +} + +void +RtspControllerParent::ActorDestroy(ActorDestroyReason why) +{ + LOG(("RtspControllerParent::ActorDestroy()")); + mIPCOpen = false; + + NS_ENSURE_TRUE_VOID(mController); + mController->Stop(); + mController = nullptr; +} + +bool +RtspControllerParent::RecvAsyncOpen(const URIParams& aURI) +{ + LOG(("RtspControllerParent::RecvAsyncOpen()")); + + mURI = DeserializeURI(aURI); + + mController = new RtspController(nullptr); + mController->Init(mURI); + nsresult rv = mController->AsyncOpen(this); + if (NS_SUCCEEDED(rv)) return true; + + mController = nullptr; + return SendAsyncOpenFailed(rv); +} + +bool +RtspControllerParent::RecvPlay() +{ + LOG(("RtspControllerParent::RecvPlay()")); + NS_ENSURE_TRUE(mController, NS_ERROR_NOT_INITIALIZED); + + nsresult rv = mController->Play(); + NS_ENSURE_SUCCESS(rv, false); + return true; +} + +bool +RtspControllerParent::RecvPause() +{ + LOG(("RtspControllerParent::RecvPause()")); + NS_ENSURE_TRUE(mController, NS_ERROR_NOT_INITIALIZED); + + nsresult rv = mController->Pause(); + NS_ENSURE_SUCCESS(rv, false); + return true; +} + +bool +RtspControllerParent::RecvResume() +{ + LOG(("RtspControllerParent::RecvResume()")); + NS_ENSURE_TRUE(mController, NS_ERROR_NOT_INITIALIZED); + + nsresult rv = mController->Resume(); + NS_ENSURE_SUCCESS(rv, false); + return true; +} + +bool +RtspControllerParent::RecvSuspend() +{ + LOG(("RtspControllerParent::RecvSuspend()")); + NS_ENSURE_TRUE(mController, NS_ERROR_NOT_INITIALIZED); + + nsresult rv = mController->Suspend(); + NS_ENSURE_SUCCESS(rv, false); + return true; +} + +bool +RtspControllerParent::RecvSeek(const uint64_t& offset) +{ + LOG(("RtspControllerParent::RecvSeek()")); + NS_ENSURE_TRUE(mController, NS_ERROR_NOT_INITIALIZED); + + nsresult rv = mController->Seek(offset); + NS_ENSURE_SUCCESS(rv, false); + return true; +} + +bool +RtspControllerParent::RecvStop() +{ + LOG(("RtspControllerParent::RecvStop()")); + NS_ENSURE_TRUE(mController, NS_ERROR_NOT_INITIALIZED); + + nsresult rv = mController->Stop(); + NS_ENSURE_SUCCESS(rv, false); + return true; +} + +NS_IMETHODIMP +RtspControllerParent::OnMediaDataAvailable(uint8_t index, + const nsACString & data, + uint32_t length, + uint32_t offset, + nsIStreamingProtocolMetaData *meta) +{ + NS_ENSURE_ARG_POINTER(meta); + uint32_t int32Value; + uint64_t int64Value; + + LOG(("RtspControllerParent:: OnMediaDataAvailable %d:%d time %lld", + index, length, int64Value)); + + // Serialize meta data. + nsCString name; + name.AssignLiteral("TIMESTAMP"); + nsresult rv = meta->GetTimeStamp(&int64Value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + InfallibleTArray<RtspMetadataParam> metaData; + metaData.AppendElement(RtspMetadataParam(name, int64Value)); + + name.AssignLiteral("FRAMETYPE"); + rv = meta->GetFrameType(&int32Value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + metaData.AppendElement(RtspMetadataParam(name, int32Value)); + + nsCString stream; + stream.Assign(data); + if (!mIPCOpen || + !SendOnMediaDataAvailable(index, stream, length, offset, metaData)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +RtspControllerParent::OnConnected(uint8_t index, + nsIStreamingProtocolMetaData *meta) +{ + NS_ENSURE_ARG_POINTER(meta); + uint32_t int32Value; + uint64_t int64Value; + + LOG(("RtspControllerParent:: OnConnected")); + // Serialize meta data. + InfallibleTArray<RtspMetadataParam> metaData; + nsCString name; + name.AssignLiteral("TRACKS"); + nsresult rv = meta->GetTotalTracks(&int32Value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + metaData.AppendElement(RtspMetadataParam(name, int32Value)); + + name.AssignLiteral("MIMETYPE"); + nsCString mimeType; + rv = meta->GetMimeType(mimeType); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + metaData.AppendElement(RtspMetadataParam(name, mimeType)); + + name.AssignLiteral("WIDTH"); + rv = meta->GetWidth(&int32Value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + metaData.AppendElement(RtspMetadataParam(name, int32Value)); + + name.AssignLiteral("HEIGHT"); + rv = meta->GetHeight(&int32Value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + metaData.AppendElement(RtspMetadataParam(name, int32Value)); + + name.AssignLiteral("DURATION"); + rv = meta->GetDuration(&int64Value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + metaData.AppendElement(RtspMetadataParam(name, int64Value)); + + name.AssignLiteral("SAMPLERATE"); + rv = meta->GetSampleRate(&int32Value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + metaData.AppendElement(RtspMetadataParam(name, int32Value)); + + name.AssignLiteral("TIMESTAMP"); + rv = meta->GetTimeStamp(&int64Value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + metaData.AppendElement(RtspMetadataParam(name, int64Value)); + + name.AssignLiteral("CHANNELCOUNT"); + rv = meta->GetChannelCount(&int32Value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + metaData.AppendElement(RtspMetadataParam(name, int32Value)); + + nsCString esds; + rv = meta->GetEsdsData(esds); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + name.AssignLiteral("ESDS"); + metaData.AppendElement(RtspMetadataParam(name, esds)); + + nsCString avcc; + rv = meta->GetAvccData(avcc); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + name.AssignLiteral("AVCC"); + metaData.AppendElement(RtspMetadataParam(name, avcc)); + + if (!mIPCOpen || !SendOnConnected(index, metaData)) { + return NS_ERROR_FAILURE; + } + + return NS_OK; +} + +NS_IMETHODIMP +RtspControllerParent::OnDisconnected(uint8_t index, + uint32_t reason) +{ + LOG(("RtspControllerParent::OnDisconnected()")); + if (!mIPCOpen || !SendOnDisconnected(index, reason)) { + return NS_ERROR_FAILURE; + } + return NS_OK; +} + +NS_IMETHODIMP +RtspControllerParent::GetInterface(const nsIID & iid, void **result) +{ + LOG(("RtspControllerParent::GetInterface()")); + return QueryInterface(iid, result); +} + +} // namespace net +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/controller/RtspControllerParent.h @@ -0,0 +1,55 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RtspControllerParent_h +#define RtspControllerParent_h + +#include "mozilla/net/PRtspControllerParent.h" +#include "mozilla/net/NeckoParent.h" +#include "nsIStreamingProtocolController.h" +#include "nsIInterfaceRequestor.h" +#include "nsILoadContext.h" +#include "nsIURI.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +class nsIAuthPromptProvider; + +namespace mozilla { +namespace net { + +class RtspControllerParent : public PRtspControllerParent + , public nsIInterfaceRequestor + , public nsIStreamingProtocolListener +{ + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSIINTERFACEREQUESTOR + NS_DECL_NSISTREAMINGPROTOCOLLISTENER + + RtspControllerParent(); + ~RtspControllerParent(); + + bool RecvAsyncOpen(const URIParams& aURI); + bool RecvPlay(); + bool RecvPause(); + bool RecvResume(); + bool RecvSuspend(); + bool RecvSeek(const uint64_t& offset); + bool RecvStop(); + + private: + bool mIPCOpen; + void ActorDestroy(ActorDestroyReason why); + // RTSP URL refer to a stream or an aggregate of streams. + nsCOMPtr<nsIURI> mURI; + // The nsIStreamingProtocolController implementation. + nsCOMPtr<nsIStreamingProtocolController> mController; +}; + +} // namespace net +} // namespace mozilla +#endif // RtspControllerParent_h
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/controller/RtspMetaData.cpp @@ -0,0 +1,249 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#include "RtspMetaData.h" +#include "prlog.h" + +using namespace mozilla; + +namespace mozilla { +namespace net { + +NS_IMPL_ISUPPORTS1(RtspMetaData, nsIStreamingProtocolMetaData) + +RtspMetaData::RtspMetaData() + : mIndex(0) + , mWidth(0) + , mHeight(0) + , mDuration(0) + , mSampleRate(0) + , mCurrentTimeStamp(0) + , mChannelCount(0) +{ + mMimeType.AssignLiteral("NONE"); +} + +RtspMetaData::~RtspMetaData() +{ + +} + +nsresult +RtspMetaData::DeserializeRtspMetaData(const InfallibleTArray<RtspMetadataParam>& metaArray) +{ + nsresult rv; + + // Deserialize meta data. + for (uint32_t i = 0; i < metaArray.Length(); i++) { + const RtspMetaValue& value = metaArray[i].value(); + const nsCString& name = metaArray[i].name(); + + if (name.EqualsLiteral("FRAMETYPE")) { + rv = SetFrameType(value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } else if (name.EqualsLiteral("TIMESTAMP")) { + rv = SetTimeStamp(value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } else if (name.EqualsLiteral("TRACKS")) { + rv = SetTotalTracks(value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } else if(name.EqualsLiteral("MIMETYPE")) { + rv = SetMimeType(value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } else if (name.EqualsLiteral("WIDTH")) { + rv = SetWidth(value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } else if (name.EqualsLiteral("HEIGHT")) { + rv = SetHeight(value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } else if (name.EqualsLiteral("SAMPLERATE")) { + rv = SetSampleRate(value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } else if(name.EqualsLiteral("DURATION")) { + rv = SetDuration(value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } else if (name.EqualsLiteral("CHANNELCOUNT")) { + rv = SetChannelCount(value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } else if (name.EqualsLiteral("ESDS")) { + rv = SetEsdsData(value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } else if (name.EqualsLiteral("AVCC")) { + rv = SetAvccData(value); + NS_ENSURE_SUCCESS(rv, NS_ERROR_FAILURE); + } + } + + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::GetFrameType(uint32_t *aFrameType) +{ + NS_ENSURE_ARG_POINTER(aFrameType); + *aFrameType = mFrameType; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::SetFrameType(uint32_t aFrameType) +{ + mFrameType = aFrameType; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::GetTotalTracks(uint32_t *aTracks) +{ + NS_ENSURE_ARG_POINTER(aTracks); + *aTracks = mTotalTracks; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::SetTotalTracks(uint32_t aTracks) +{ + mTotalTracks = aTracks; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::GetMimeType(nsACString & aMimeType) +{ + aMimeType.Assign(mMimeType); + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::SetMimeType(const nsACString & aMimeType) +{ + mMimeType.Assign(aMimeType); + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::GetWidth(uint32_t *aWidth) +{ + NS_ENSURE_ARG_POINTER(aWidth); + *aWidth = mWidth; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::SetWidth(uint32_t aWidth) +{ + mWidth = aWidth; + return NS_OK; +} + +/* attribute unsigned long height; */ +NS_IMETHODIMP +RtspMetaData::GetHeight(uint32_t *aHeight) +{ + NS_ENSURE_ARG_POINTER(aHeight); + *aHeight = mHeight; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::SetHeight(uint32_t aHeight) +{ + mHeight = aHeight; + return NS_OK; +} + +/* attribute unsigned long long duration; */ +NS_IMETHODIMP +RtspMetaData::GetDuration(uint64_t *aDuration) +{ + NS_ENSURE_ARG_POINTER(aDuration); + *aDuration = mDuration; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::SetDuration(uint64_t aDuration) +{ + mDuration = aDuration; + return NS_OK; +} + +/* attribute unsigned long sampleRate; */ +NS_IMETHODIMP +RtspMetaData::GetSampleRate(uint32_t *aSampleRate) +{ + NS_ENSURE_ARG_POINTER(aSampleRate); + *aSampleRate = mSampleRate; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::SetSampleRate(uint32_t aSampleRate) +{ + mSampleRate = aSampleRate; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::GetTimeStamp(uint64_t *aTimeStamp) +{ + NS_ENSURE_ARG_POINTER(aTimeStamp); + *aTimeStamp = mCurrentTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::SetTimeStamp(uint64_t aTimeStamp) +{ + mCurrentTimeStamp = aTimeStamp; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::GetChannelCount(uint32_t *aChannelCount) +{ + NS_ENSURE_ARG_POINTER(aChannelCount); + *aChannelCount = mChannelCount; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::SetChannelCount(uint32_t aChannelCount) +{ + mChannelCount = aChannelCount; + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::GetEsdsData(nsACString & aESDS) +{ + aESDS.Assign(mESDS); + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::SetEsdsData(const nsACString & aESDS) +{ + mESDS.Assign(aESDS); + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::GetAvccData(nsACString & aAVCC) +{ + aAVCC.Assign(mAVCC); + return NS_OK; +} + +NS_IMETHODIMP +RtspMetaData::SetAvccData(const nsACString & aAVCC) +{ + mAVCC.Assign(aAVCC); + return NS_OK; +} + +} // namespace mozilla::net +} // namespace mozilla
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/controller/RtspMetaData.h @@ -0,0 +1,47 @@ +/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */ +/* vim: set sw=2 ts=8 et tw=80 : */ +/* This Source Code Form is subject to the terms of the Mozilla Public + * License, v. 2.0. If a copy of the MPL was not distributed with this + * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ + +#ifndef RtspMetaData_h +#define RtspMetaData_h + +#include "mozilla/net/PRtspController.h" +#include "nsIStreamingProtocolController.h" +#include "nsCOMPtr.h" +#include "nsString.h" + +namespace mozilla { +namespace net { + +class RtspMetaData : public nsIStreamingProtocolMetaData +{ + public: + NS_DECL_THREADSAFE_ISUPPORTS + NS_DECL_NSISTREAMINGPROTOCOLMETADATA + + RtspMetaData(); + ~RtspMetaData(); + + nsresult DeserializeRtspMetaData(const InfallibleTArray<RtspMetadataParam>& metaArray); + + private: + uint32_t mFrameType; + uint32_t mIndex; + uint32_t mTotalTracks; + uint32_t mWidth; + uint32_t mHeight; + uint64_t mDuration; + uint32_t mSampleRate; + uint64_t mCurrentTimeStamp; + uint32_t mChannelCount; + nsCString mMimeType; + nsCString mESDS; + nsCString mAVCC; +}; + +} // namespace net +} // namespace mozilla + +#endif // RtspMetaData_h
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/moz.build @@ -0,0 +1,49 @@ +# vim: set filetype=python: +# This Source Code Form is subject to the terms of the Mozilla Public +# License, v. 2.0. If a copy of the MPL was not distributed with this +# file, You can obtain one at http://mozilla.org/MPL/2.0/. + +XPIDL_MODULE = 'necko_rtsp' + +MODULE = 'necko' + +EXPORTS.mozilla.net += [ + 'RtspChannel.h', + 'RtspHandler.h', + 'controller/RtspController.h', + 'controller/RtspControllerChild.h', + 'controller/RtspControllerParent.h', + 'controller/RtspMetaData.h', + 'rtsp/RTSPSource.h', +] + +CPP_SOURCES += [ + 'RtspChannel.cpp', + 'RtspHandler.cpp', + 'controller/RtspController.cpp', + 'controller/RtspControllerChild.cpp', + 'controller/RtspControllerParent.cpp', + 'controller/RtspMetaData.cpp', + 'rtsp/AAMRAssembler.cpp', + 'rtsp/AAVCAssembler.cpp', + 'rtsp/AH263Assembler.cpp', + 'rtsp/AMPEG4AudioAssembler.cpp', + 'rtsp/AMPEG4ElementaryAssembler.cpp', + 'rtsp/APacketSource.cpp', + 'rtsp/ARTPAssembler.cpp', + 'rtsp/ARTPConnection.cpp', + 'rtsp/ARTPSource.cpp', + 'rtsp/ARTPWriter.cpp', + 'rtsp/ARTSPConnection.cpp', + 'rtsp/ARawAudioAssembler.cpp', + 'rtsp/ASessionDescription.cpp', + 'rtsp/RTSPSource.cpp', +] + +FAIL_ON_WARNINGS = True + +LIBXUL_LIBRARY = True + +MSVC_ENABLE_PGO = True + +LIBRARY_NAME = 'nkrtsp_s'
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/AAMRAssembler.cpp @@ -0,0 +1,234 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "AAMRAssembler" +#include <utils/Log.h> + +#include "AAMRAssembler.h" + +#include "ARTPSource.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/Utils.h> + +namespace android { + +static bool GetAttribute(const char *s, const char *key, AString *value) { + value->clear(); + + size_t keyLen = strlen(key); + + for (;;) { + const char *colonPos = strchr(s, ';'); + + size_t len = + (colonPos == NULL) ? strlen(s) : colonPos - s; + + if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) { + value->setTo(&s[keyLen + 1], len - keyLen - 1); + return true; + } + if (len == keyLen && !strncmp(s, key, keyLen)) { + value->setTo("1"); + return true; + } + + if (colonPos == NULL) { + return false; + } + + s = colonPos + 1; + } +} + +AAMRAssembler::AAMRAssembler( + const sp<AMessage> ¬ify, bool isWide, const AString ¶ms) + : mIsWide(isWide), + mNotifyMsg(notify), + mNextExpectedSeqNoValid(false), + mNextExpectedSeqNo(0) { + AString value; + CHECK(GetAttribute(params.c_str(), "octet-align", &value) && value == "1"); + CHECK(!GetAttribute(params.c_str(), "crc", &value) || value == "0"); + CHECK(!GetAttribute(params.c_str(), "interleaving", &value)); +} + +AAMRAssembler::~AAMRAssembler() { +} + +ARTPAssembler::AssemblyStatus AAMRAssembler::assembleMore( + const sp<ARTPSource> &source) { + return addPacket(source); +} + +static size_t getFrameSize(bool isWide, unsigned FT) { + static const size_t kFrameSizeNB[9] = { + 95, 103, 118, 134, 148, 159, 204, 244, 39 + }; + static const size_t kFrameSizeWB[10] = { + 132, 177, 253, 285, 317, 365, 397, 461, 477, 40 + }; + + if (FT == 15) { + return 1; + } + + size_t frameSize = isWide ? kFrameSizeWB[FT] : kFrameSizeNB[FT]; + + // Round up bits to bytes and add 1 for the header byte. + frameSize = (frameSize + 7) / 8 + 1; + + return frameSize; +} + +ARTPAssembler::AssemblyStatus AAMRAssembler::addPacket( + const sp<ARTPSource> &source) { + List<sp<ABuffer> > *queue = source->queue(); + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + + if (mNextExpectedSeqNoValid) { + List<sp<ABuffer> >::iterator it = queue->begin(); + while (it != queue->end()) { + if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) { + break; + } + + it = queue->erase(it); + } + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + } + + sp<ABuffer> buffer = *queue->begin(); + + if (!mNextExpectedSeqNoValid) { + mNextExpectedSeqNoValid = true; + mNextExpectedSeqNo = (uint32_t)buffer->int32Data(); + } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) { + LOGV("Not the sequence number I expected"); + + return WRONG_SEQUENCE_NUMBER; + } + + // hexdump(buffer->data(), buffer->size()); + + if (buffer->size() < 1) { + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + LOGV("AMR packet too short."); + + return MALFORMED_PACKET; + } + + unsigned payloadHeader = buffer->data()[0]; + unsigned CMR = payloadHeader >> 4; + CHECK_EQ(payloadHeader & 0x0f, 0u); // RR + + Vector<uint8_t> tableOfContents; + + size_t offset = 1; + size_t totalSize = 0; + for (;;) { + if (offset >= buffer->size()) { + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + LOGV("Unable to parse TOC."); + + return MALFORMED_PACKET; + } + + uint8_t toc = buffer->data()[offset++]; + + unsigned FT = (toc >> 3) & 0x0f; + if ((toc & 3) != 0 + || (mIsWide && FT > 9 && FT != 15) + || (!mIsWide && FT > 8 && FT != 15)) { + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + LOGV("Illegal TOC entry."); + + return MALFORMED_PACKET; + } + + totalSize += getFrameSize(mIsWide, (toc >> 3) & 0x0f); + + tableOfContents.push(toc); + + if (0 == (toc & 0x80)) { + break; + } + } + + sp<ABuffer> accessUnit = new ABuffer(totalSize); + CopyTimes(accessUnit, buffer); + + size_t dstOffset = 0; + for (size_t i = 0; i < tableOfContents.size(); ++i) { + uint8_t toc = tableOfContents[i]; + + size_t frameSize = getFrameSize(mIsWide, (toc >> 3) & 0x0f); + + if (offset + frameSize - 1 > buffer->size()) { + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + LOGV("AMR packet too short."); + + return MALFORMED_PACKET; + } + + accessUnit->data()[dstOffset++] = toc; + memcpy(accessUnit->data() + dstOffset, + buffer->data() + offset, frameSize - 1); + + offset += frameSize - 1; + dstOffset += frameSize - 1; + } + + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setObject("access-unit", accessUnit); + msg->post(); + + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + return OK; +} + +void AAMRAssembler::packetLost() { + CHECK(mNextExpectedSeqNoValid); + ++mNextExpectedSeqNo; +} + +void AAMRAssembler::onByeReceived() { + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setInt32("eos", true); + msg->post(); +} + +} // namespace android
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/AAMRAssembler.h @@ -0,0 +1,59 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_AMR_ASSEMBLER_H_ + +#define A_AMR_ASSEMBLER_H_ + +#include "ARTPAssembler.h" + +#include <utils/List.h> + +#include <stdint.h> + +namespace android { + +struct AMessage; +struct AString; + +struct AAMRAssembler : public ARTPAssembler { + AAMRAssembler( + const sp<AMessage> ¬ify, bool isWide, + const AString ¶ms); + +protected: + virtual ~AAMRAssembler(); + + virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source); + virtual void onByeReceived(); + virtual void packetLost(); + +private: + bool mIsWide; + + sp<AMessage> mNotifyMsg; + bool mNextExpectedSeqNoValid; + uint32_t mNextExpectedSeqNo; + + AssemblyStatus addPacket(const sp<ARTPSource> &source); + + DISALLOW_EVIL_CONSTRUCTORS(AAMRAssembler); +}; + +} // namespace android + +#endif // A_AMR_ASSEMBLER_H_ +
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/AAVCAssembler.cpp @@ -0,0 +1,376 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "AAVCAssembler" +#include <utils/Log.h> + +#include "AAVCAssembler.h" + +#include "ARTPSource.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> + +#include <stdint.h> + +namespace android { + +// static +AAVCAssembler::AAVCAssembler(const sp<AMessage> ¬ify) + : mNotifyMsg(notify), + mAccessUnitRTPTime(0), + mNextExpectedSeqNoValid(false), + mNextExpectedSeqNo(0), + mAccessUnitDamaged(false) { +} + +AAVCAssembler::~AAVCAssembler() { +} + +ARTPAssembler::AssemblyStatus AAVCAssembler::addNALUnit( + const sp<ARTPSource> &source) { + List<sp<ABuffer> > *queue = source->queue(); + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + + if (mNextExpectedSeqNoValid) { + List<sp<ABuffer> >::iterator it = queue->begin(); + while (it != queue->end()) { + if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) { + break; + } + + it = queue->erase(it); + } + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + } + + sp<ABuffer> buffer = *queue->begin(); + + if (!mNextExpectedSeqNoValid) { + mNextExpectedSeqNoValid = true; + mNextExpectedSeqNo = (uint32_t)buffer->int32Data(); + } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) { + LOGV("Not the sequence number I expected"); + + return WRONG_SEQUENCE_NUMBER; + } + + const uint8_t *data = buffer->data(); + size_t size = buffer->size(); + + if (size < 1 || (data[0] & 0x80)) { + // Corrupt. + + LOGV("Ignoring corrupt buffer."); + queue->erase(queue->begin()); + + ++mNextExpectedSeqNo; + return MALFORMED_PACKET; + } + + unsigned nalType = data[0] & 0x1f; + if (nalType >= 1 && nalType <= 23) { + addSingleNALUnit(buffer); + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + return OK; + } else if (nalType == 28) { + // FU-A + return addFragmentedNALUnit(queue); + } else if (nalType == 24) { + // STAP-A + bool success = addSingleTimeAggregationPacket(buffer); + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + return success ? OK : MALFORMED_PACKET; + } else { + LOGV("Ignoring unsupported buffer (nalType=%d)", nalType); + + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + return MALFORMED_PACKET; + } +} + +void AAVCAssembler::addSingleNALUnit(const sp<ABuffer> &buffer) { + LOGV("addSingleNALUnit of size %d", buffer->size()); +#if !LOG_NDEBUG + hexdump(buffer->data(), buffer->size()); +#endif + + uint32_t rtpTime; + CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime)); + + if (!mNALUnits.empty() && rtpTime != mAccessUnitRTPTime) { + submitAccessUnit(); + } + mAccessUnitRTPTime = rtpTime; + + mNALUnits.push_back(buffer); +} + +bool AAVCAssembler::addSingleTimeAggregationPacket(const sp<ABuffer> &buffer) { + const uint8_t *data = buffer->data(); + size_t size = buffer->size(); + + if (size < 3) { + LOGV("Discarding too small STAP-A packet."); + return false; + } + + ++data; + --size; + while (size >= 2) { + size_t nalSize = (data[0] << 8) | data[1]; + + if (size < nalSize + 2) { + LOGV("Discarding malformed STAP-A packet."); + return false; + } + + sp<ABuffer> unit = new ABuffer(nalSize); + memcpy(unit->data(), &data[2], nalSize); + + CopyTimes(unit, buffer); + + addSingleNALUnit(unit); + + data += 2 + nalSize; + size -= 2 + nalSize; + } + + if (size != 0) { + LOGV("Unexpected padding at end of STAP-A packet."); + } + + return true; +} + +ARTPAssembler::AssemblyStatus AAVCAssembler::addFragmentedNALUnit( + List<sp<ABuffer> > *queue) { + CHECK(!queue->empty()); + + sp<ABuffer> buffer = *queue->begin(); + const uint8_t *data = buffer->data(); + size_t size = buffer->size(); + + CHECK(size > 0); + unsigned indicator = data[0]; + + CHECK((indicator & 0x1f) == 28); + + if (size < 2) { + LOGV("Ignoring malformed FU buffer (size = %d)", size); + + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + return MALFORMED_PACKET; + } + + if (!(data[1] & 0x80)) { + // Start bit not set on the first buffer. + + LOGV("Start bit not set on first buffer"); + + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + return MALFORMED_PACKET; + } + + uint32_t nalType = data[1] & 0x1f; + uint32_t nri = (data[0] >> 5) & 3; + + uint32_t expectedSeqNo = (uint32_t)buffer->int32Data() + 1; + size_t totalSize = size - 2; + size_t totalCount = 1; + bool complete = false; + + if (data[1] & 0x40) { + // Huh? End bit also set on the first buffer. + + LOGV("Grrr. This isn't fragmented at all."); + + complete = true; + } else { + List<sp<ABuffer> >::iterator it = ++queue->begin(); + while (it != queue->end()) { + LOGV("sequence length %d", totalCount); + + const sp<ABuffer> &buffer = *it; + + const uint8_t *data = buffer->data(); + size_t size = buffer->size(); + + if ((uint32_t)buffer->int32Data() != expectedSeqNo) { + LOGV("sequence not complete, expected seqNo %d, got %d", + expectedSeqNo, (uint32_t)buffer->int32Data()); + + return WRONG_SEQUENCE_NUMBER; + } + + if (size < 2 + || data[0] != indicator + || (data[1] & 0x1f) != nalType + || (data[1] & 0x80)) { + LOGV("Ignoring malformed FU buffer."); + + // Delete the whole start of the FU. + + it = queue->begin(); + for (size_t i = 0; i <= totalCount; ++i) { + it = queue->erase(it); + } + + mNextExpectedSeqNo = expectedSeqNo + 1; + + return MALFORMED_PACKET; + } + + totalSize += size - 2; + ++totalCount; + + expectedSeqNo = expectedSeqNo + 1; + + if (data[1] & 0x40) { + // This is the last fragment. + complete = true; + break; + } + + ++it; + } + } + + if (!complete) { + return NOT_ENOUGH_DATA; + } + + mNextExpectedSeqNo = expectedSeqNo; + + // We found all the fragments that make up the complete NAL unit. + + // Leave room for the header. So far totalSize did not include the + // header byte. + ++totalSize; + + sp<ABuffer> unit = new ABuffer(totalSize); + CopyTimes(unit, *queue->begin()); + + unit->data()[0] = (nri << 5) | nalType; + + size_t offset = 1; + List<sp<ABuffer> >::iterator it = queue->begin(); + for (size_t i = 0; i < totalCount; ++i) { + const sp<ABuffer> &buffer = *it; + + LOGV("piece #%d/%d", i + 1, totalCount); +#if !LOG_NDEBUG + hexdump(buffer->data(), buffer->size()); +#endif + + memcpy(unit->data() + offset, buffer->data() + 2, buffer->size() - 2); + offset += buffer->size() - 2; + + it = queue->erase(it); + } + + unit->setRange(0, totalSize); + + addSingleNALUnit(unit); + + LOGV("successfully assembled a NAL unit from fragments."); + + return OK; +} + +void AAVCAssembler::submitAccessUnit() { + CHECK(!mNALUnits.empty()); + + LOGV("Access unit complete (%d nal units)", mNALUnits.size()); + + size_t totalSize = 0; + for (List<sp<ABuffer> >::iterator it = mNALUnits.begin(); + it != mNALUnits.end(); ++it) { + totalSize += 4 + (*it)->size(); + } + + sp<ABuffer> accessUnit = new ABuffer(totalSize); + size_t offset = 0; + for (List<sp<ABuffer> >::iterator it = mNALUnits.begin(); + it != mNALUnits.end(); ++it) { + memcpy(accessUnit->data() + offset, "\x00\x00\x00\x01", 4); + offset += 4; + + sp<ABuffer> nal = *it; + memcpy(accessUnit->data() + offset, nal->data(), nal->size()); + offset += nal->size(); + } + + CopyTimes(accessUnit, *mNALUnits.begin()); + +#if 0 + printf(mAccessUnitDamaged ? "X" : "."); + fflush(stdout); +#endif + + if (mAccessUnitDamaged) { + accessUnit->meta()->setInt32("damaged", true); + } + + mNALUnits.clear(); + mAccessUnitDamaged = false; + + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setObject("access-unit", accessUnit); + msg->post(); +} + +ARTPAssembler::AssemblyStatus AAVCAssembler::assembleMore( + const sp<ARTPSource> &source) { + AssemblyStatus status = addNALUnit(source); + if (status == MALFORMED_PACKET) { + mAccessUnitDamaged = true; + } + return status; +} + +void AAVCAssembler::packetLost() { + CHECK(mNextExpectedSeqNoValid); + LOGV("packetLost (expected %d)", mNextExpectedSeqNo); + + ++mNextExpectedSeqNo; + + mAccessUnitDamaged = true; +} + +void AAVCAssembler::onByeReceived() { + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setInt32("eos", true); + msg->post(); +} + +} // namespace android
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/AAVCAssembler.h @@ -0,0 +1,62 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_AVC_ASSEMBLER_H_ + +#define A_AVC_ASSEMBLER_H_ + +#include "ARTPAssembler.h" + +#include <utils/List.h> +#include <utils/RefBase.h> + +namespace android { + +struct ABuffer; +struct AMessage; + +struct AAVCAssembler : public ARTPAssembler { + AAVCAssembler(const sp<AMessage> ¬ify); + +protected: + virtual ~AAVCAssembler(); + + virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source); + virtual void onByeReceived(); + virtual void packetLost(); + +private: + sp<AMessage> mNotifyMsg; + + uint32_t mAccessUnitRTPTime; + bool mNextExpectedSeqNoValid; + uint32_t mNextExpectedSeqNo; + bool mAccessUnitDamaged; + List<sp<ABuffer> > mNALUnits; + + AssemblyStatus addNALUnit(const sp<ARTPSource> &source); + void addSingleNALUnit(const sp<ABuffer> &buffer); + AssemblyStatus addFragmentedNALUnit(List<sp<ABuffer> > *queue); + bool addSingleTimeAggregationPacket(const sp<ABuffer> &buffer); + + void submitAccessUnit(); + + DISALLOW_EVIL_CONSTRUCTORS(AAVCAssembler); +}; + +} // namespace android + +#endif // A_AVC_ASSEMBLER_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/AH263Assembler.cpp @@ -0,0 +1,187 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "AH263Assembler.h" + +#include "ARTPSource.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/Utils.h> + +namespace android { + +AH263Assembler::AH263Assembler(const sp<AMessage> ¬ify) + : mNotifyMsg(notify), + mAccessUnitRTPTime(0), + mNextExpectedSeqNoValid(false), + mNextExpectedSeqNo(0), + mAccessUnitDamaged(false) { +} + +AH263Assembler::~AH263Assembler() { +} + +ARTPAssembler::AssemblyStatus AH263Assembler::assembleMore( + const sp<ARTPSource> &source) { + AssemblyStatus status = addPacket(source); + if (status == MALFORMED_PACKET) { + mAccessUnitDamaged = true; + } + return status; +} + +ARTPAssembler::AssemblyStatus AH263Assembler::addPacket( + const sp<ARTPSource> &source) { + List<sp<ABuffer> > *queue = source->queue(); + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + + if (mNextExpectedSeqNoValid) { + List<sp<ABuffer> >::iterator it = queue->begin(); + while (it != queue->end()) { + if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) { + break; + } + + it = queue->erase(it); + } + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + } + + sp<ABuffer> buffer = *queue->begin(); + + if (!mNextExpectedSeqNoValid) { + mNextExpectedSeqNoValid = true; + mNextExpectedSeqNo = (uint32_t)buffer->int32Data(); + } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) { +#if VERBOSE + LOG(VERBOSE) << "Not the sequence number I expected"; +#endif + + return WRONG_SEQUENCE_NUMBER; + } + + uint32_t rtpTime; + CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime)); + + if (mPackets.size() > 0 && rtpTime != mAccessUnitRTPTime) { + submitAccessUnit(); + } + mAccessUnitRTPTime = rtpTime; + + // hexdump(buffer->data(), buffer->size()); + + if (buffer->size() < 2) { + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + return MALFORMED_PACKET; + } + + unsigned payloadHeader = U16_AT(buffer->data()); + CHECK_EQ(payloadHeader >> 11, 0u); // RR=0 + unsigned P = (payloadHeader >> 10) & 1; + CHECK_EQ((payloadHeader >> 9) & 1, 0u); // V=0 + CHECK_EQ((payloadHeader >> 3) & 0x3f, 0u); // PLEN=0 + CHECK_EQ(payloadHeader & 7, 0u); // PEBIT=0 + + if (P) { + buffer->data()[0] = 0x00; + buffer->data()[1] = 0x00; + } else { + buffer->setRange(buffer->offset() + 2, buffer->size() - 2); + } + + mPackets.push_back(buffer); + + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + return OK; +} + +void AH263Assembler::submitAccessUnit() { + CHECK(!mPackets.empty()); + +#if VERBOSE + LOG(VERBOSE) << "Access unit complete (" << mPackets.size() << " packets)"; +#endif + + size_t totalSize = 0; + List<sp<ABuffer> >::iterator it = mPackets.begin(); + while (it != mPackets.end()) { + const sp<ABuffer> &unit = *it; + + totalSize += unit->size(); + ++it; + } + + sp<ABuffer> accessUnit = new ABuffer(totalSize); + size_t offset = 0; + it = mPackets.begin(); + while (it != mPackets.end()) { + const sp<ABuffer> &unit = *it; + + memcpy((uint8_t *)accessUnit->data() + offset, + unit->data(), unit->size()); + + offset += unit->size(); + + ++it; + } + + CopyTimes(accessUnit, *mPackets.begin()); + +#if 0 + printf(mAccessUnitDamaged ? "X" : "."); + fflush(stdout); +#endif + + if (mAccessUnitDamaged) { + accessUnit->meta()->setInt32("damaged", true); + } + + mPackets.clear(); + mAccessUnitDamaged = false; + + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setObject("access-unit", accessUnit); + msg->post(); +} + +void AH263Assembler::packetLost() { + CHECK(mNextExpectedSeqNoValid); + ++mNextExpectedSeqNo; + + mAccessUnitDamaged = true; +} + +void AH263Assembler::onByeReceived() { + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setInt32("eos", true); + msg->post(); +} + +} // namespace android +
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/AH263Assembler.h @@ -0,0 +1,57 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_H263_ASSEMBLER_H_ + +#define A_H263_ASSEMBLER_H_ + +#include "ARTPAssembler.h" + +#include <utils/List.h> + +#include <stdint.h> + +namespace android { + +struct AMessage; + +struct AH263Assembler : public ARTPAssembler { + AH263Assembler(const sp<AMessage> ¬ify); + +protected: + virtual ~AH263Assembler(); + + virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source); + virtual void onByeReceived(); + virtual void packetLost(); + +private: + sp<AMessage> mNotifyMsg; + uint32_t mAccessUnitRTPTime; + bool mNextExpectedSeqNoValid; + uint32_t mNextExpectedSeqNo; + bool mAccessUnitDamaged; + List<sp<ABuffer> > mPackets; + + AssemblyStatus addPacket(const sp<ARTPSource> &source); + void submitAccessUnit(); + + DISALLOW_EVIL_CONSTRUCTORS(AH263Assembler); +}; + +} // namespace android + +#endif // A_H263_ASSEMBLER_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/AMPEG4AudioAssembler.cpp @@ -0,0 +1,591 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "AMPEG4AudioAssembler" + +#include "AMPEG4AudioAssembler.h" + +#include "ARTPSource.h" + +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/foundation/ABitReader.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/MediaErrors.h> + +#include <ctype.h> + +namespace android { + +static bool GetAttribute(const char *s, const char *key, AString *value) { + value->clear(); + + size_t keyLen = strlen(key); + + for (;;) { + while (isspace(*s)) { + ++s; + } + + const char *colonPos = strchr(s, ';'); + + size_t len = + (colonPos == NULL) ? strlen(s) : colonPos - s; + + if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) { + value->setTo(&s[keyLen + 1], len - keyLen - 1); + return true; + } + + if (colonPos == NULL) { + return false; + } + + s = colonPos + 1; + } +} + +static sp<ABuffer> decodeHex(const AString &s) { + if ((s.size() % 2) != 0) { + return NULL; + } + + size_t outLen = s.size() / 2; + sp<ABuffer> buffer = new ABuffer(outLen); + uint8_t *out = buffer->data(); + + uint8_t accum = 0; + for (size_t i = 0; i < s.size(); ++i) { + char c = s.c_str()[i]; + unsigned value; + if (c >= '0' && c <= '9') { + value = c - '0'; + } else if (c >= 'a' && c <= 'f') { + value = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + value = c - 'A' + 10; + } else { + return NULL; + } + + accum = (accum << 4) | value; + + if (i & 1) { + *out++ = accum; + + accum = 0; + } + } + + return buffer; +} + +static status_t parseAudioObjectType( + ABitReader *bits, unsigned *audioObjectType) { + *audioObjectType = bits->getBits(5); + if ((*audioObjectType) == 31) { + *audioObjectType = 32 + bits->getBits(6); + } + + return OK; +} + +static status_t parseGASpecificConfig( + ABitReader *bits, + unsigned audioObjectType, unsigned channelConfiguration) { + unsigned frameLengthFlag = bits->getBits(1); + unsigned dependsOnCoreCoder = bits->getBits(1); + if (dependsOnCoreCoder) { + /* unsigned coreCoderDelay = */bits->getBits(1); + } + unsigned extensionFlag = bits->getBits(1); + + if (!channelConfiguration) { + // program_config_element + return ERROR_UNSUPPORTED; // XXX to be implemented + } + + if (audioObjectType == 6 || audioObjectType == 20) { + /* unsigned layerNr = */bits->getBits(3); + } + + if (extensionFlag) { + if (audioObjectType == 22) { + /* unsigned numOfSubFrame = */bits->getBits(5); + /* unsigned layerLength = */bits->getBits(11); + } else if (audioObjectType == 17 || audioObjectType == 19 + || audioObjectType == 20 || audioObjectType == 23) { + /* unsigned aacSectionDataResilienceFlag = */bits->getBits(1); + /* unsigned aacScalefactorDataResilienceFlag = */bits->getBits(1); + /* unsigned aacSpectralDataResilienceFlag = */bits->getBits(1); + } + + unsigned extensionFlag3 = bits->getBits(1); + CHECK_EQ(extensionFlag3, 0u); // TBD in version 3 + } + + return OK; +} + +static status_t parseAudioSpecificConfig(ABitReader *bits, sp<ABuffer> *asc) { + const uint8_t *dataStart = bits->data(); + size_t totalNumBits = bits->numBitsLeft(); + + unsigned audioObjectType; + CHECK_EQ(parseAudioObjectType(bits, &audioObjectType), (status_t)OK); + + unsigned samplingFreqIndex = bits->getBits(4); + if (samplingFreqIndex == 0x0f) { + /* unsigned samplingFrequency = */bits->getBits(24); + } + + unsigned channelConfiguration = bits->getBits(4); + + unsigned extensionAudioObjectType = 0; + unsigned sbrPresent = 0; + + if (audioObjectType == 5) { + extensionAudioObjectType = audioObjectType; + sbrPresent = 1; + unsigned extensionSamplingFreqIndex = bits->getBits(4); + if (extensionSamplingFreqIndex == 0x0f) { + /* unsigned extensionSamplingFrequency = */bits->getBits(24); + } + CHECK_EQ(parseAudioObjectType(bits, &audioObjectType), (status_t)OK); + } + + CHECK((audioObjectType >= 1 && audioObjectType <= 4) + || (audioObjectType >= 6 && audioObjectType <= 7) + || audioObjectType == 17 + || (audioObjectType >= 19 && audioObjectType <= 23)); + + CHECK_EQ(parseGASpecificConfig( + bits, audioObjectType, channelConfiguration), (status_t)OK); + + if (audioObjectType == 17 + || (audioObjectType >= 19 && audioObjectType <= 27)) { + unsigned epConfig = bits->getBits(2); + if (epConfig == 2 || epConfig == 3) { + // ErrorProtectionSpecificConfig + return ERROR_UNSUPPORTED; // XXX to be implemented + + if (epConfig == 3) { + unsigned directMapping = bits->getBits(1); + CHECK_EQ(directMapping, 1u); + } + } + } + + if (extensionAudioObjectType != 5 && bits->numBitsLeft() >= 16) { + size_t numBitsLeftAtStart = bits->numBitsLeft(); + + unsigned syncExtensionType = bits->getBits(11); + if (syncExtensionType == 0x2b7) { + LOGI("found syncExtension"); + + CHECK_EQ(parseAudioObjectType(bits, &extensionAudioObjectType), + (status_t)OK); + + sbrPresent = bits->getBits(1); + + if (sbrPresent == 1) { + unsigned extensionSamplingFreqIndex = bits->getBits(4); + if (extensionSamplingFreqIndex == 0x0f) { + /* unsigned extensionSamplingFrequency = */bits->getBits(24); + } + } + + size_t numBitsInExtension = + numBitsLeftAtStart - bits->numBitsLeft(); + + if (numBitsInExtension & 7) { + // Apparently an extension is always considered an even + // multiple of 8 bits long. + + LOGI("Skipping %d bits after sync extension", + 8 - (numBitsInExtension & 7)); + + bits->skipBits(8 - (numBitsInExtension & 7)); + } + } else { + bits->putBits(syncExtensionType, 11); + } + } + + if (asc != NULL) { + size_t bitpos = totalNumBits & 7; + + ABitReader bs(dataStart, (totalNumBits + 7) / 8); + + totalNumBits -= bits->numBitsLeft(); + + size_t numBytes = (totalNumBits + 7) / 8; + + *asc = new ABuffer(numBytes); + + if (bitpos & 7) { + bs.skipBits(8 - (bitpos & 7)); + } + + uint8_t *dstPtr = (*asc)->data(); + while (numBytes > 0) { + *dstPtr++ = bs.getBits(8); + --numBytes; + } + } + + return OK; +} + +static status_t parseStreamMuxConfig( + ABitReader *bits, + unsigned *numSubFrames, + unsigned *frameLengthType, + ssize_t *fixedFrameLength, + bool *otherDataPresent, + unsigned *otherDataLenBits) { + unsigned audioMuxVersion = bits->getBits(1); + + unsigned audioMuxVersionA = 0; + if (audioMuxVersion == 1) { + audioMuxVersionA = bits->getBits(1); + } + + CHECK_EQ(audioMuxVersionA, 0u); // otherwise future spec + + if (audioMuxVersion != 0) { + return ERROR_UNSUPPORTED; // XXX to be implemented; + } + CHECK_EQ(audioMuxVersion, 0u); // XXX to be implemented + + unsigned allStreamsSameTimeFraming = bits->getBits(1); + CHECK_EQ(allStreamsSameTimeFraming, 1u); // There's only one stream. + + *numSubFrames = bits->getBits(6); + unsigned numProgram = bits->getBits(4); + CHECK_EQ(numProgram, 0u); // disabled in RTP LATM + + unsigned numLayer = bits->getBits(3); + CHECK_EQ(numLayer, 0u); // disabled in RTP LATM + + if (audioMuxVersion == 0) { + // AudioSpecificConfig + CHECK_EQ(parseAudioSpecificConfig(bits, NULL /* asc */), (status_t)OK); + } else { + TRESPASS(); // XXX to be implemented + } + + *frameLengthType = bits->getBits(3); + *fixedFrameLength = -1; + + switch (*frameLengthType) { + case 0: + { + /* unsigned bufferFullness = */bits->getBits(8); + + // The "coreFrameOffset" does not apply since there's only + // a single layer. + break; + } + + case 1: + { + *fixedFrameLength = bits->getBits(9); + break; + } + + case 2: + { + // reserved + TRESPASS(); + break; + } + + case 3: + case 4: + case 5: + { + /* unsigned CELPframeLengthTableIndex = */bits->getBits(6); + break; + } + + case 6: + case 7: + { + /* unsigned HVXCframeLengthTableIndex = */bits->getBits(1); + break; + } + + default: + break; + } + + *otherDataPresent = bits->getBits(1); + *otherDataLenBits = 0; + if (*otherDataPresent) { + if (audioMuxVersion == 1) { + TRESPASS(); // XXX to be implemented + } else { + *otherDataLenBits = 0; + + unsigned otherDataLenEsc; + do { + (*otherDataLenBits) <<= 8; + otherDataLenEsc = bits->getBits(1); + unsigned otherDataLenTmp = bits->getBits(8); + (*otherDataLenBits) += otherDataLenTmp; + } while (otherDataLenEsc); + } + } + + unsigned crcCheckPresent = bits->getBits(1); + if (crcCheckPresent) { + /* unsigned crcCheckSum = */bits->getBits(8); + } + + return OK; +} + +sp<ABuffer> AMPEG4AudioAssembler::removeLATMFraming(const sp<ABuffer> &buffer) { + CHECK(!mMuxConfigPresent); // XXX to be implemented + + sp<ABuffer> out = new ABuffer(buffer->size()); + out->setRange(0, 0); + + size_t offset = 0; + uint8_t *ptr = buffer->data(); + + for (size_t i = 0; i <= mNumSubFrames; ++i) { + // parse PayloadLengthInfo + + unsigned payloadLength = 0; + + switch (mFrameLengthType) { + case 0: + { + unsigned muxSlotLengthBytes = 0; + unsigned tmp; + do { + CHECK_LT(offset, buffer->size()); + tmp = ptr[offset++]; + muxSlotLengthBytes += tmp; + } while (tmp == 0xff); + + payloadLength = muxSlotLengthBytes; + break; + } + + case 2: + { + // reserved + + TRESPASS(); + break; + } + + default: + { + CHECK_GE(mFixedFrameLength, 0); + + payloadLength = mFixedFrameLength; + break; + } + } + + CHECK_LE(offset + payloadLength, buffer->size()); + + memcpy(out->data() + out->size(), &ptr[offset], payloadLength); + out->setRange(0, out->size() + payloadLength); + + offset += payloadLength; + + if (mOtherDataPresent) { + // We want to stay byte-aligned. + + CHECK((mOtherDataLenBits % 8) == 0); + CHECK_LE(offset + (mOtherDataLenBits / 8), buffer->size()); + offset += mOtherDataLenBits / 8; + } + } + + if (offset < buffer->size()) { + LOGI("ignoring %d bytes of trailing data", buffer->size() - offset); + } + CHECK_LE(offset, buffer->size()); + + return out; +} + +AMPEG4AudioAssembler::AMPEG4AudioAssembler( + const sp<AMessage> ¬ify, const AString ¶ms) + : mNotifyMsg(notify), + mMuxConfigPresent(false), + mAccessUnitRTPTime(0), + mNextExpectedSeqNoValid(false), + mNextExpectedSeqNo(0), + mAccessUnitDamaged(false) { + AString val; + if (!GetAttribute(params.c_str(), "cpresent", &val)) { + mMuxConfigPresent = true; + } else if (val == "0") { + mMuxConfigPresent = false; + } else { + CHECK(val == "1"); + mMuxConfigPresent = true; + } + + CHECK(GetAttribute(params.c_str(), "config", &val)); + + sp<ABuffer> config = decodeHex(val); + CHECK(config != NULL); + + ABitReader bits(config->data(), config->size()); + status_t err = parseStreamMuxConfig( + &bits, &mNumSubFrames, &mFrameLengthType, + &mFixedFrameLength, + &mOtherDataPresent, &mOtherDataLenBits); + + CHECK_EQ(err, (status_t)NO_ERROR); +} + +AMPEG4AudioAssembler::~AMPEG4AudioAssembler() { +} + +ARTPAssembler::AssemblyStatus AMPEG4AudioAssembler::assembleMore( + const sp<ARTPSource> &source) { + AssemblyStatus status = addPacket(source); + if (status == MALFORMED_PACKET) { + mAccessUnitDamaged = true; + } + return status; +} + +ARTPAssembler::AssemblyStatus AMPEG4AudioAssembler::addPacket( + const sp<ARTPSource> &source) { + List<sp<ABuffer> > *queue = source->queue(); + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + + if (mNextExpectedSeqNoValid) { + List<sp<ABuffer> >::iterator it = queue->begin(); + while (it != queue->end()) { + if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) { + break; + } + + it = queue->erase(it); + } + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + } + + sp<ABuffer> buffer = *queue->begin(); + + if (!mNextExpectedSeqNoValid) { + mNextExpectedSeqNoValid = true; + mNextExpectedSeqNo = (uint32_t)buffer->int32Data(); + } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) { +#if VERBOSE + LOG(VERBOSE) << "Not the sequence number I expected"; +#endif + + return WRONG_SEQUENCE_NUMBER; + } + + uint32_t rtpTime; + CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime)); + + if (mPackets.size() > 0 && rtpTime != mAccessUnitRTPTime) { + submitAccessUnit(); + } + mAccessUnitRTPTime = rtpTime; + + mPackets.push_back(buffer); + + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + return OK; +} + +void AMPEG4AudioAssembler::submitAccessUnit() { + CHECK(!mPackets.empty()); + +#if VERBOSE + LOG(VERBOSE) << "Access unit complete (" << mPackets.size() << " packets)"; +#endif + + size_t totalSize = 0; + List<sp<ABuffer> >::iterator it = mPackets.begin(); + while (it != mPackets.end()) { + const sp<ABuffer> &unit = *it; + + totalSize += unit->size(); + ++it; + } + + sp<ABuffer> accessUnit = new ABuffer(totalSize); + size_t offset = 0; + it = mPackets.begin(); + while (it != mPackets.end()) { + const sp<ABuffer> &unit = *it; + + memcpy((uint8_t *)accessUnit->data() + offset, + unit->data(), unit->size()); + + ++it; + } + + accessUnit = removeLATMFraming(accessUnit); + CopyTimes(accessUnit, *mPackets.begin()); + +#if 0 + printf(mAccessUnitDamaged ? "X" : "."); + fflush(stdout); +#endif + + if (mAccessUnitDamaged) { + accessUnit->meta()->setInt32("damaged", true); + } + + mPackets.clear(); + mAccessUnitDamaged = false; + + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setObject("access-unit", accessUnit); + msg->post(); +} + +void AMPEG4AudioAssembler::packetLost() { + CHECK(mNextExpectedSeqNoValid); + ++mNextExpectedSeqNo; + + mAccessUnitDamaged = true; +} + +void AMPEG4AudioAssembler::onByeReceived() { + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setInt32("eos", true); + msg->post(); +} + +} // namespace android
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/AMPEG4AudioAssembler.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_MPEG4_AUDIO_ASSEMBLER_H_ + +#define A_MPEG4_AUDIO_ASSEMBLER_H_ + +#include "ARTPAssembler.h" + +#include <utils/List.h> + +#include <stdint.h> + +namespace android { + +struct AMessage; +struct AString; + +struct AMPEG4AudioAssembler : public ARTPAssembler { + AMPEG4AudioAssembler( + const sp<AMessage> ¬ify, const AString ¶ms); + +protected: + virtual ~AMPEG4AudioAssembler(); + + virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source); + virtual void onByeReceived(); + virtual void packetLost(); + +private: + sp<AMessage> mNotifyMsg; + + bool mMuxConfigPresent; + unsigned mNumSubFrames; + unsigned mFrameLengthType; + ssize_t mFixedFrameLength; + bool mOtherDataPresent; + unsigned mOtherDataLenBits; + + uint32_t mAccessUnitRTPTime; + bool mNextExpectedSeqNoValid; + uint32_t mNextExpectedSeqNo; + bool mAccessUnitDamaged; + List<sp<ABuffer> > mPackets; + + AssemblyStatus addPacket(const sp<ARTPSource> &source); + void submitAccessUnit(); + + sp<ABuffer> removeLATMFraming(const sp<ABuffer> &buffer); + + DISALLOW_EVIL_CONSTRUCTORS(AMPEG4AudioAssembler); +}; + +} // namespace android + +#endif // A_MPEG4_AUDIO_ASSEMBLER_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/AMPEG4ElementaryAssembler.cpp @@ -0,0 +1,399 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "AMPEG4ElementaryAssembler" +#include <utils/Log.h> + +#include "AMPEG4ElementaryAssembler.h" + +#include "ARTPSource.h" + +#include <media/stagefright/foundation/ABitReader.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/Utils.h> + +#include <ctype.h> +#include <stdint.h> + +namespace android { + +static bool GetAttribute(const char *s, const char *key, AString *value) { + value->clear(); + + size_t keyLen = strlen(key); + + for (;;) { + while (isspace(*s)) { + ++s; + } + + const char *colonPos = strchr(s, ';'); + + size_t len = + (colonPos == NULL) ? strlen(s) : colonPos - s; + + if (len >= keyLen + 1 && s[keyLen] == '=' + && !strncasecmp(s, key, keyLen)) { + value->setTo(&s[keyLen + 1], len - keyLen - 1); + return true; + } + + if (colonPos == NULL) { + return false; + } + + s = colonPos + 1; + } +} + +static bool GetIntegerAttribute( + const char *s, const char *key, unsigned *x) { + *x = 0; + + AString val; + if (!GetAttribute(s, key, &val)) { + return false; + } + + s = val.c_str(); + char *end; + unsigned y = strtoul(s, &end, 10); + + if (end == s || *end != '\0') { + return false; + } + + *x = y; + + return true; +} + +// static +AMPEG4ElementaryAssembler::AMPEG4ElementaryAssembler( + const sp<AMessage> ¬ify, const AString &desc, const AString ¶ms) + : mNotifyMsg(notify), + mIsGeneric(false), + mParams(params), + mSizeLength(0), + mIndexLength(0), + mIndexDeltaLength(0), + mCTSDeltaLength(0), + mDTSDeltaLength(0), + mRandomAccessIndication(false), + mStreamStateIndication(0), + mAuxiliaryDataSizeLength(0), + mHasAUHeader(false), + mAccessUnitRTPTime(0), + mNextExpectedSeqNoValid(false), + mNextExpectedSeqNo(0), + mAccessUnitDamaged(false) { + mIsGeneric = !strncasecmp(desc.c_str(),"mpeg4-generic/", 14); + + if (mIsGeneric) { + AString value; + CHECK(GetAttribute(params.c_str(), "mode", &value)); + + if (!GetIntegerAttribute(params.c_str(), "sizeLength", &mSizeLength)) { + mSizeLength = 0; + } + + if (!GetIntegerAttribute( + params.c_str(), "indexLength", &mIndexLength)) { + mIndexLength = 0; + } + + if (!GetIntegerAttribute( + params.c_str(), "indexDeltaLength", &mIndexDeltaLength)) { + mIndexDeltaLength = 0; + } + + if (!GetIntegerAttribute( + params.c_str(), "CTSDeltaLength", &mCTSDeltaLength)) { + mCTSDeltaLength = 0; + } + + if (!GetIntegerAttribute( + params.c_str(), "DTSDeltaLength", &mDTSDeltaLength)) { + mDTSDeltaLength = 0; + } + + unsigned x; + if (!GetIntegerAttribute( + params.c_str(), "randomAccessIndication", &x)) { + mRandomAccessIndication = false; + } else { + CHECK(x == 0 || x == 1); + mRandomAccessIndication = (x != 0); + } + + if (!GetIntegerAttribute( + params.c_str(), "streamStateIndication", + &mStreamStateIndication)) { + mStreamStateIndication = 0; + } + + if (!GetIntegerAttribute( + params.c_str(), "auxiliaryDataSizeLength", + &mAuxiliaryDataSizeLength)) { + mAuxiliaryDataSizeLength = 0; + } + + mHasAUHeader = + mSizeLength > 0 + || mIndexLength > 0 + || mIndexDeltaLength > 0 + || mCTSDeltaLength > 0 + || mDTSDeltaLength > 0 + || mRandomAccessIndication + || mStreamStateIndication > 0; + } +} + +AMPEG4ElementaryAssembler::~AMPEG4ElementaryAssembler() { +} + +struct AUHeader { + unsigned mSize; + unsigned mSerial; +}; + +ARTPAssembler::AssemblyStatus AMPEG4ElementaryAssembler::addPacket( + const sp<ARTPSource> &source) { + List<sp<ABuffer> > *queue = source->queue(); + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + + if (mNextExpectedSeqNoValid) { + List<sp<ABuffer> >::iterator it = queue->begin(); + while (it != queue->end()) { + if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) { + break; + } + + it = queue->erase(it); + } + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + } + + sp<ABuffer> buffer = *queue->begin(); + + if (!mNextExpectedSeqNoValid) { + mNextExpectedSeqNoValid = true; + mNextExpectedSeqNo = (uint32_t)buffer->int32Data(); + } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) { + LOGV("Not the sequence number I expected"); + + return WRONG_SEQUENCE_NUMBER; + } + + uint32_t rtpTime; + CHECK(buffer->meta()->findInt32("rtp-time", (int32_t *)&rtpTime)); + + if (mPackets.size() > 0 && rtpTime != mAccessUnitRTPTime) { + submitAccessUnit(); + } + mAccessUnitRTPTime = rtpTime; + + if (!mIsGeneric) { + mPackets.push_back(buffer); + } else { + // hexdump(buffer->data(), buffer->size()); + + CHECK_GE(buffer->size(), 2u); + unsigned AU_headers_length = U16_AT(buffer->data()); // in bits + + CHECK_GE(buffer->size(), 2 + (AU_headers_length + 7) / 8); + + List<AUHeader> headers; + + ABitReader bits(buffer->data() + 2, buffer->size() - 2); + unsigned numBitsLeft = AU_headers_length; + + unsigned AU_serial = 0; + for (;;) { + if (numBitsLeft < mSizeLength) { break; } + + unsigned AU_size = bits.getBits(mSizeLength); + numBitsLeft -= mSizeLength; + + size_t n = headers.empty() ? mIndexLength : mIndexDeltaLength; + if (numBitsLeft < n) { break; } + + unsigned AU_index = bits.getBits(n); + numBitsLeft -= n; + + if (headers.empty()) { + AU_serial = AU_index; + } else { + AU_serial += 1 + AU_index; + } + + if (mCTSDeltaLength > 0) { + if (numBitsLeft < 1) { + break; + } + --numBitsLeft; + if (bits.getBits(1)) { + if (numBitsLeft < mCTSDeltaLength) { + break; + } + bits.skipBits(mCTSDeltaLength); + numBitsLeft -= mCTSDeltaLength; + } + } + + if (mDTSDeltaLength > 0) { + if (numBitsLeft < 1) { + break; + } + --numBitsLeft; + if (bits.getBits(1)) { + if (numBitsLeft < mDTSDeltaLength) { + break; + } + bits.skipBits(mDTSDeltaLength); + numBitsLeft -= mDTSDeltaLength; + } + } + + if (mRandomAccessIndication) { + if (numBitsLeft < 1) { + break; + } + bits.skipBits(1); + --numBitsLeft; + } + + if (mStreamStateIndication > 0) { + if (numBitsLeft < mStreamStateIndication) { + break; + } + bits.skipBits(mStreamStateIndication); + } + + AUHeader header; + header.mSize = AU_size; + header.mSerial = AU_serial; + headers.push_back(header); + } + + size_t offset = 2 + (AU_headers_length + 7) / 8; + + if (mAuxiliaryDataSizeLength > 0) { + ABitReader bits(buffer->data() + offset, buffer->size() - offset); + + unsigned auxSize = bits.getBits(mAuxiliaryDataSizeLength); + + offset += (mAuxiliaryDataSizeLength + auxSize + 7) / 8; + } + + for (List<AUHeader>::iterator it = headers.begin(); + it != headers.end(); ++it) { + const AUHeader &header = *it; + + CHECK_LE(offset + header.mSize, buffer->size()); + + sp<ABuffer> accessUnit = new ABuffer(header.mSize); + memcpy(accessUnit->data(), buffer->data() + offset, header.mSize); + + offset += header.mSize; + + CopyTimes(accessUnit, buffer); + mPackets.push_back(accessUnit); + } + + CHECK_EQ(offset, buffer->size()); + } + + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + return OK; +} + +void AMPEG4ElementaryAssembler::submitAccessUnit() { + CHECK(!mPackets.empty()); + + LOGV("Access unit complete (%d nal units)", mPackets.size()); + + size_t totalSize = 0; + for (List<sp<ABuffer> >::iterator it = mPackets.begin(); + it != mPackets.end(); ++it) { + totalSize += (*it)->size(); + } + + sp<ABuffer> accessUnit = new ABuffer(totalSize); + size_t offset = 0; + for (List<sp<ABuffer> >::iterator it = mPackets.begin(); + it != mPackets.end(); ++it) { + sp<ABuffer> nal = *it; + memcpy(accessUnit->data() + offset, nal->data(), nal->size()); + offset += nal->size(); + } + + CopyTimes(accessUnit, *mPackets.begin()); + +#if 0 + printf(mAccessUnitDamaged ? "X" : "."); + fflush(stdout); +#endif + + if (mAccessUnitDamaged) { + accessUnit->meta()->setInt32("damaged", true); + } + + mPackets.clear(); + mAccessUnitDamaged = false; + + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setObject("access-unit", accessUnit); + msg->post(); +} + +ARTPAssembler::AssemblyStatus AMPEG4ElementaryAssembler::assembleMore( + const sp<ARTPSource> &source) { + AssemblyStatus status = addPacket(source); + if (status == MALFORMED_PACKET) { + mAccessUnitDamaged = true; + } + return status; +} + +void AMPEG4ElementaryAssembler::packetLost() { + CHECK(mNextExpectedSeqNoValid); + LOGV("packetLost (expected %d)", mNextExpectedSeqNo); + + ++mNextExpectedSeqNo; + + mAccessUnitDamaged = true; +} + +void AMPEG4ElementaryAssembler::onByeReceived() { + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setInt32("eos", true); + msg->post(); +} + +} // namespace android
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/AMPEG4ElementaryAssembler.h @@ -0,0 +1,74 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_MPEG4_ELEM_ASSEMBLER_H_ + +#define A_MPEG4_ELEM_ASSEMBLER_H_ + +#include "ARTPAssembler.h" + +#include <media/stagefright/foundation/AString.h> + +#include <utils/List.h> +#include <utils/RefBase.h> + +namespace android { + +struct ABuffer; +struct AMessage; + +struct AMPEG4ElementaryAssembler : public ARTPAssembler { + AMPEG4ElementaryAssembler( + const sp<AMessage> ¬ify, const AString &desc, + const AString ¶ms); + +protected: + virtual ~AMPEG4ElementaryAssembler(); + + virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source); + virtual void onByeReceived(); + virtual void packetLost(); + +private: + sp<AMessage> mNotifyMsg; + bool mIsGeneric; + AString mParams; + + unsigned mSizeLength; + unsigned mIndexLength; + unsigned mIndexDeltaLength; + unsigned mCTSDeltaLength; + unsigned mDTSDeltaLength; + bool mRandomAccessIndication; + unsigned mStreamStateIndication; + unsigned mAuxiliaryDataSizeLength; + bool mHasAUHeader; + + uint32_t mAccessUnitRTPTime; + bool mNextExpectedSeqNoValid; + uint32_t mNextExpectedSeqNo; + bool mAccessUnitDamaged; + List<sp<ABuffer> > mPackets; + + AssemblyStatus addPacket(const sp<ARTPSource> &source); + void submitAccessUnit(); + + DISALLOW_EVIL_CONSTRUCTORS(AMPEG4ElementaryAssembler); +}; + +} // namespace android + +#endif // A_MPEG4_ELEM_ASSEMBLER_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/APacketSource.cpp @@ -0,0 +1,580 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "APacketSource" +#include <utils/Log.h> + +#include "APacketSource.h" + +#include "ARawAudioAssembler.h" +#include "ASessionDescription.h" + +#include "avc_utils.h" + +#include <ctype.h> + +#include <media/stagefright/foundation/ABitReader.h> +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/AString.h> +#include <media/stagefright/foundation/base64.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaErrors.h> +#include <media/stagefright/MetaData.h> +#include <utils/Vector.h> + +namespace android { + +static bool GetAttribute(const char *s, const char *key, AString *value) { + value->clear(); + + size_t keyLen = strlen(key); + + for (;;) { + while (isspace(*s)) { + ++s; + } + + const char *colonPos = strchr(s, ';'); + + size_t len = + (colonPos == NULL) ? strlen(s) : colonPos - s; + + if (len >= keyLen + 1 && s[keyLen] == '=' && !strncmp(s, key, keyLen)) { + value->setTo(&s[keyLen + 1], len - keyLen - 1); + return true; + } + + if (colonPos == NULL) { + return false; + } + + s = colonPos + 1; + } +} + +static sp<ABuffer> decodeHex(const AString &s) { + if ((s.size() % 2) != 0) { + return NULL; + } + + size_t outLen = s.size() / 2; + sp<ABuffer> buffer = new ABuffer(outLen); + uint8_t *out = buffer->data(); + + uint8_t accum = 0; + for (size_t i = 0; i < s.size(); ++i) { + char c = s.c_str()[i]; + unsigned value; + if (c >= '0' && c <= '9') { + value = c - '0'; + } else if (c >= 'a' && c <= 'f') { + value = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + value = c - 'A' + 10; + } else { + return NULL; + } + + accum = (accum << 4) | value; + + if (i & 1) { + *out++ = accum; + + accum = 0; + } + } + + return buffer; +} + +static sp<ABuffer> MakeAVCCodecSpecificData( + const char *params, int32_t *width, int32_t *height) { + *width = 0; + *height = 0; + + AString val; + if (!GetAttribute(params, "profile-level-id", &val)) { + return NULL; + } + + sp<ABuffer> profileLevelID = decodeHex(val); + CHECK(profileLevelID != NULL); + CHECK_EQ(profileLevelID->size(), 3u); + + Vector<sp<ABuffer> > paramSets; + + size_t numSeqParameterSets = 0; + size_t totalSeqParameterSetSize = 0; + size_t numPicParameterSets = 0; + size_t totalPicParameterSetSize = 0; + + if (!GetAttribute(params, "sprop-parameter-sets", &val)) { + return NULL; + } + + size_t start = 0; + for (;;) { + ssize_t commaPos = val.find(",", start); + size_t end = (commaPos < 0) ? val.size() : commaPos; + + AString nalString(val, start, end - start); + sp<ABuffer> nal = decodeBase64(nalString); + CHECK(nal != NULL); + CHECK_GT(nal->size(), 0u); + CHECK_LE(nal->size(), 65535u); + + uint8_t nalType = nal->data()[0] & 0x1f; + if (numSeqParameterSets == 0) { + CHECK_EQ((unsigned)nalType, 7u); + } else if (numPicParameterSets > 0) { + CHECK_EQ((unsigned)nalType, 8u); + } + if (nalType == 7) { + ++numSeqParameterSets; + totalSeqParameterSetSize += nal->size(); + } else { + CHECK_EQ((unsigned)nalType, 8u); + ++numPicParameterSets; + totalPicParameterSetSize += nal->size(); + } + + paramSets.push(nal); + + if (commaPos < 0) { + break; + } + + start = commaPos + 1; + } + + CHECK_LT(numSeqParameterSets, 32u); + CHECK_LE(numPicParameterSets, 255u); + + size_t csdSize = + 1 + 3 + 1 + 1 + + 2 * numSeqParameterSets + totalSeqParameterSetSize + + 1 + 2 * numPicParameterSets + totalPicParameterSetSize; + + sp<ABuffer> csd = new ABuffer(csdSize); + uint8_t *out = csd->data(); + + *out++ = 0x01; // configurationVersion + memcpy(out, profileLevelID->data(), 3); + out += 3; + *out++ = (0x3f << 2) | 1; // lengthSize == 2 bytes + *out++ = 0xe0 | numSeqParameterSets; + + for (size_t i = 0; i < numSeqParameterSets; ++i) { + sp<ABuffer> nal = paramSets.editItemAt(i); + + *out++ = nal->size() >> 8; + *out++ = nal->size() & 0xff; + + memcpy(out, nal->data(), nal->size()); + + out += nal->size(); + + if (i == 0) { + FindAVCDimensions(nal, width, height); + LOGI("dimensions %dx%d", *width, *height); + } + } + + *out++ = numPicParameterSets; + + for (size_t i = 0; i < numPicParameterSets; ++i) { + sp<ABuffer> nal = paramSets.editItemAt(i + numSeqParameterSets); + + *out++ = nal->size() >> 8; + *out++ = nal->size() & 0xff; + + memcpy(out, nal->data(), nal->size()); + + out += nal->size(); + } + + // hexdump(csd->data(), csd->size()); + + return csd; +} + +sp<ABuffer> MakeAACCodecSpecificData(const char *params) { + AString val; + CHECK(GetAttribute(params, "config", &val)); + + sp<ABuffer> config = decodeHex(val); + CHECK(config != NULL); + CHECK_GE(config->size(), 4u); + + const uint8_t *data = config->data(); + uint32_t x = data[0] << 24 | data[1] << 16 | data[2] << 8 | data[3]; + x = (x >> 1) & 0xffff; + + static const uint8_t kStaticESDS[] = { + 0x03, 22, + 0x00, 0x00, // ES_ID + 0x00, // streamDependenceFlag, URL_Flag, OCRstreamFlag + + 0x04, 17, + 0x40, // Audio ISO/IEC 14496-3 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + 0x05, 2, + // AudioSpecificInfo follows + }; + + sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + 2); + memcpy(csd->data(), kStaticESDS, sizeof(kStaticESDS)); + csd->data()[sizeof(kStaticESDS)] = (x >> 8) & 0xff; + csd->data()[sizeof(kStaticESDS) + 1] = x & 0xff; + + // hexdump(csd->data(), csd->size()); + + return csd; +} + +// From mpeg4-generic configuration data. +sp<ABuffer> MakeAACCodecSpecificData2(const char *params) { + AString val; + unsigned long objectType; + if (GetAttribute(params, "objectType", &val)) { + const char *s = val.c_str(); + char *end; + objectType = strtoul(s, &end, 10); + CHECK(end > s && *end == '\0'); + } else { + objectType = 0x40; // Audio ISO/IEC 14496-3 + } + + CHECK(GetAttribute(params, "config", &val)); + + sp<ABuffer> config = decodeHex(val); + CHECK(config != NULL); + + // Make sure size fits into a single byte and doesn't have to + // be encoded. + CHECK_LT(20 + config->size(), 128u); + + const uint8_t *data = config->data(); + + static const uint8_t kStaticESDS[] = { + 0x03, 22, + 0x00, 0x00, // ES_ID + 0x00, // streamDependenceFlag, URL_Flag, OCRstreamFlag + + 0x04, 17, + 0x40, // Audio ISO/IEC 14496-3 + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + 0x00, 0x00, 0x00, 0x00, + + 0x05, 2, + // AudioSpecificInfo follows + }; + + sp<ABuffer> csd = new ABuffer(sizeof(kStaticESDS) + config->size()); + uint8_t *dst = csd->data(); + *dst++ = 0x03; + *dst++ = 20 + config->size(); + *dst++ = 0x00; // ES_ID + *dst++ = 0x00; + *dst++ = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag + *dst++ = 0x04; + *dst++ = 15 + config->size(); + *dst++ = objectType; + for (int i = 0; i < 12; ++i) { *dst++ = 0x00; } + *dst++ = 0x05; + *dst++ = config->size(); + memcpy(dst, config->data(), config->size()); + + // hexdump(csd->data(), csd->size()); + + return csd; +} + +static size_t GetSizeWidth(size_t x) { + size_t n = 1; + while (x > 127) { + ++n; + x >>= 7; + } + return n; +} + +static uint8_t *EncodeSize(uint8_t *dst, size_t x) { + while (x > 127) { + *dst++ = (x & 0x7f) | 0x80; + x >>= 7; + } + *dst++ = x; + return dst; +} + +static bool ExtractDimensionsMPEG4Config( + const sp<ABuffer> &config, int32_t *width, int32_t *height) { + *width = 0; + *height = 0; + + const uint8_t *ptr = config->data(); + size_t offset = 0; + bool foundVOL = false; + while (offset + 3 < config->size()) { + if (memcmp("\x00\x00\x01", &ptr[offset], 3) + || (ptr[offset + 3] & 0xf0) != 0x20) { + ++offset; + continue; + } + + foundVOL = true; + break; + } + + if (!foundVOL) { + return false; + } + + return ExtractDimensionsFromVOLHeader( + &ptr[offset], config->size() - offset, width, height); +} + +static sp<ABuffer> MakeMPEG4VideoCodecSpecificData( + const char *params, int32_t *width, int32_t *height) { + *width = 0; + *height = 0; + + AString val; + CHECK(GetAttribute(params, "config", &val)); + + sp<ABuffer> config = decodeHex(val); + CHECK(config != NULL); + + if (!ExtractDimensionsMPEG4Config(config, width, height)) { + return NULL; + } + + LOGI("VOL dimensions = %dx%d", *width, *height); + + size_t len1 = config->size() + GetSizeWidth(config->size()) + 1; + size_t len2 = len1 + GetSizeWidth(len1) + 1 + 13; + size_t len3 = len2 + GetSizeWidth(len2) + 1 + 3; + + sp<ABuffer> csd = new ABuffer(len3); + uint8_t *dst = csd->data(); + *dst++ = 0x03; + dst = EncodeSize(dst, len2 + 3); + *dst++ = 0x00; // ES_ID + *dst++ = 0x00; + *dst++ = 0x00; // streamDependenceFlag, URL_Flag, OCRstreamFlag + + *dst++ = 0x04; + dst = EncodeSize(dst, len1 + 13); + *dst++ = 0x01; // Video ISO/IEC 14496-2 Simple Profile + for (size_t i = 0; i < 12; ++i) { + *dst++ = 0x00; + } + + *dst++ = 0x05; + dst = EncodeSize(dst, config->size()); + memcpy(dst, config->data(), config->size()); + dst += config->size(); + + // hexdump(csd->data(), csd->size()); + + return csd; +} + +APacketSource::APacketSource( + const sp<ASessionDescription> &sessionDesc, size_t index) + : mInitCheck(NO_INIT), + mFormat(new MetaData) { + unsigned long PT; + AString desc; + AString params; + sessionDesc->getFormatType(index, &PT, &desc, ¶ms); + + int64_t durationUs; + if (sessionDesc->getDurationUs(&durationUs)) { + mFormat->setInt64(kKeyDuration, durationUs); + } else { + // Set its value to zero(long long) to indicate that this is a live stream. + mFormat->setInt64(kKeyDuration, 0ll); + } + + mInitCheck = OK; + if (!strncmp(desc.c_str(), "H264/", 5)) { + mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_AVC); + + int32_t width, height; + if (!sessionDesc->getDimensions(index, PT, &width, &height)) { + width = -1; + height = -1; + } + + int32_t encWidth, encHeight; + sp<ABuffer> codecSpecificData = + MakeAVCCodecSpecificData(params.c_str(), &encWidth, &encHeight); + + if (codecSpecificData != NULL) { + if (width < 0) { + // If no explicit width/height given in the sdp, use the dimensions + // extracted from the first sequence parameter set. + width = encWidth; + height = encHeight; + } + + mFormat->setData( + kKeyAVCC, 0, + codecSpecificData->data(), codecSpecificData->size()); + } else if (width < 0) { + mInitCheck = ERROR_UNSUPPORTED; + return; + } + + mFormat->setInt32(kKeyWidth, width); + mFormat->setInt32(kKeyHeight, height); + } else if (!strncmp(desc.c_str(), "H263-2000/", 10) + || !strncmp(desc.c_str(), "H263-1998/", 10)) { + mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_H263); + + int32_t width, height; + if (!sessionDesc->getDimensions(index, PT, &width, &height)) { + mInitCheck = ERROR_UNSUPPORTED; + return; + } + + mFormat->setInt32(kKeyWidth, width); + mFormat->setInt32(kKeyHeight, height); + } else if (!strncmp(desc.c_str(), "MP4A-LATM/", 10)) { + mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC); + + int32_t sampleRate, numChannels; + ASessionDescription::ParseFormatDesc( + desc.c_str(), &sampleRate, &numChannels); + + mFormat->setInt32(kKeySampleRate, sampleRate); + mFormat->setInt32(kKeyChannelCount, numChannels); + + sp<ABuffer> codecSpecificData = + MakeAACCodecSpecificData(params.c_str()); + + mFormat->setData( + kKeyESDS, 0, + codecSpecificData->data(), codecSpecificData->size()); + } else if (!strncmp(desc.c_str(), "AMR/", 4)) { + mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AMR_NB); + + int32_t sampleRate, numChannels; + ASessionDescription::ParseFormatDesc( + desc.c_str(), &sampleRate, &numChannels); + + mFormat->setInt32(kKeySampleRate, sampleRate); + mFormat->setInt32(kKeyChannelCount, numChannels); + + if (sampleRate != 8000 || numChannels != 1) { + mInitCheck = ERROR_UNSUPPORTED; + } + } else if (!strncmp(desc.c_str(), "AMR-WB/", 7)) { + mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AMR_WB); + + int32_t sampleRate, numChannels; + ASessionDescription::ParseFormatDesc( + desc.c_str(), &sampleRate, &numChannels); + + mFormat->setInt32(kKeySampleRate, sampleRate); + mFormat->setInt32(kKeyChannelCount, numChannels); + + if (sampleRate != 16000 || numChannels != 1) { + mInitCheck = ERROR_UNSUPPORTED; + } + } else if (!strncmp(desc.c_str(), "MP4V-ES/", 8)) { + mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_VIDEO_MPEG4); + + int32_t width, height; + if (!sessionDesc->getDimensions(index, PT, &width, &height)) { + width = -1; + height = -1; + } + + int32_t encWidth, encHeight; + sp<ABuffer> codecSpecificData = + MakeMPEG4VideoCodecSpecificData( + params.c_str(), &encWidth, &encHeight); + + if (codecSpecificData != NULL) { + mFormat->setData( + kKeyESDS, 0, + codecSpecificData->data(), codecSpecificData->size()); + + if (width < 0) { + width = encWidth; + height = encHeight; + } + } else if (width < 0) { + mInitCheck = ERROR_UNSUPPORTED; + return; + } + + mFormat->setInt32(kKeyWidth, width); + mFormat->setInt32(kKeyHeight, height); + } else if (!strncasecmp(desc.c_str(), "mpeg4-generic/", 14)) { + AString val; + if (!GetAttribute(params.c_str(), "mode", &val) + || (strcasecmp(val.c_str(), "AAC-lbr") + && strcasecmp(val.c_str(), "AAC-hbr"))) { + mInitCheck = ERROR_UNSUPPORTED; + return; + } + + mFormat->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_AAC); + + int32_t sampleRate, numChannels; + ASessionDescription::ParseFormatDesc( + desc.c_str(), &sampleRate, &numChannels); + + mFormat->setInt32(kKeySampleRate, sampleRate); + mFormat->setInt32(kKeyChannelCount, numChannels); + + sp<ABuffer> codecSpecificData = + MakeAACCodecSpecificData2(params.c_str()); + + mFormat->setData( + kKeyESDS, 0, + codecSpecificData->data(), codecSpecificData->size()); + } else if (ARawAudioAssembler::Supports(desc.c_str())) { + ARawAudioAssembler::MakeFormat(desc.c_str(), mFormat); + } else { + mInitCheck = ERROR_UNSUPPORTED; + } +} + +APacketSource::~APacketSource() { +} + +status_t APacketSource::initCheck() const { + return mInitCheck; +} + +sp<MetaData> APacketSource::getFormat() { + return mFormat; +} + +} // namespace android
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/APacketSource.h @@ -0,0 +1,50 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_PACKET_SOURCE_H_ + +#define A_PACKET_SOURCE_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/MetaData.h> +#include <utils/RefBase.h> + +namespace android { + +struct ASessionDescription; + +struct APacketSource : public RefBase { + APacketSource(const sp<ASessionDescription> &sessionDesc, size_t index); + + status_t initCheck() const; + + virtual sp<MetaData> getFormat(); + +protected: + virtual ~APacketSource(); + +private: + status_t mInitCheck; + + sp<MetaData> mFormat; + + DISALLOW_EVIL_CONSTRUCTORS(APacketSource); +}; + + +} // namespace android + +#endif // A_PACKET_SOURCE_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTPAssembler.cpp @@ -0,0 +1,77 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#include "ARTPAssembler.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> + +#include <stdint.h> + +namespace android { + +static int64_t getNowUs() { + struct timeval tv; + gettimeofday(&tv, NULL); + + return (int64_t)tv.tv_usec + tv.tv_sec * 1000000ll; +} + +ARTPAssembler::ARTPAssembler() + : mFirstFailureTimeUs(-1) { +} + +void ARTPAssembler::onPacketReceived(const sp<ARTPSource> &source) { + AssemblyStatus status; + for (;;) { + status = assembleMore(source); + + if (status == WRONG_SEQUENCE_NUMBER) { + if (mFirstFailureTimeUs >= 0) { + if (getNowUs() - mFirstFailureTimeUs > 10000ll) { + mFirstFailureTimeUs = -1; + + // LOG(VERBOSE) << "waited too long for packet."; + packetLost(); + continue; + } + } else { + mFirstFailureTimeUs = getNowUs(); + } + break; + } else { + mFirstFailureTimeUs = -1; + + if (status == NOT_ENOUGH_DATA) { + break; + } + } + } +} + +// static +void ARTPAssembler::CopyTimes(const sp<ABuffer> &to, const sp<ABuffer> &from) { + uint32_t rtpTime; + CHECK(from->meta()->findInt32("rtp-time", (int32_t *)&rtpTime)); + + to->meta()->setInt32("rtp-time", rtpTime); + + // Copy the seq number. + to->setInt32Data(from->int32Data()); +} + +} // namespace android
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTPAssembler.h @@ -0,0 +1,56 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_RTP_ASSEMBLER_H_ + +#define A_RTP_ASSEMBLER_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <utils/RefBase.h> + +namespace android { + +struct ABuffer; +struct ARTPSource; + +struct ARTPAssembler : public RefBase { + enum AssemblyStatus { + MALFORMED_PACKET, + WRONG_SEQUENCE_NUMBER, + NOT_ENOUGH_DATA, + OK + }; + + ARTPAssembler(); + + void onPacketReceived(const sp<ARTPSource> &source); + virtual void onByeReceived() = 0; + +protected: + virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source) = 0; + virtual void packetLost() = 0; + + static void CopyTimes(const sp<ABuffer> &to, const sp<ABuffer> &from); + +private: + int64_t mFirstFailureTimeUs; + + DISALLOW_EVIL_CONSTRUCTORS(ARTPAssembler); +}; + +} // namespace android + +#endif // A_RTP_ASSEMBLER_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTPConnection.cpp @@ -0,0 +1,676 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ARTPConnection" +#include <utils/Log.h> + +#include "ARTPConnection.h" + +#include "ARTPSource.h" +#include "ASessionDescription.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/AString.h> +#include <media/stagefright/foundation/hexdump.h> + +#include <arpa/inet.h> +#include <sys/socket.h> + +namespace android { + +static const size_t kMaxUDPSize = 1500; + +static uint16_t u16at(const uint8_t *data) { + return data[0] << 8 | data[1]; +} + +static uint32_t u32at(const uint8_t *data) { + return u16at(data) << 16 | u16at(&data[2]); +} + +static uint64_t u64at(const uint8_t *data) { + return (uint64_t)(u32at(data)) << 32 | u32at(&data[4]); +} + +// static +const int64_t ARTPConnection::kSelectTimeoutUs = 1000ll; + +struct ARTPConnection::StreamInfo { + int mRTPSocket; + int mRTCPSocket; + sp<ASessionDescription> mSessionDesc; + size_t mIndex; + sp<AMessage> mNotifyMsg; + KeyedVector<uint32_t, sp<ARTPSource> > mSources; + + int64_t mNumRTCPPacketsReceived; + int64_t mNumRTPPacketsReceived; + struct sockaddr_in mRemoteRTCPAddr; + + bool mIsInjected; +}; + +ARTPConnection::ARTPConnection(uint32_t flags) + : mFlags(flags), + mPollEventPending(false), + mLastReceiverReportTimeUs(-1) { +} + +ARTPConnection::~ARTPConnection() { +} + +void ARTPConnection::addStream( + int rtpSocket, int rtcpSocket, + const sp<ASessionDescription> &sessionDesc, + size_t index, + const sp<AMessage> ¬ify, + bool injected) { + sp<AMessage> msg = new AMessage(kWhatAddStream, id()); + msg->setInt32("rtp-socket", rtpSocket); + msg->setInt32("rtcp-socket", rtcpSocket); + msg->setObject("session-desc", sessionDesc); + msg->setSize("index", index); + msg->setMessage("notify", notify); + msg->setInt32("injected", injected); + msg->post(); +} + +void ARTPConnection::removeStream(int rtpSocket, int rtcpSocket) { + sp<AMessage> msg = new AMessage(kWhatRemoveStream, id()); + msg->setInt32("rtp-socket", rtpSocket); + msg->setInt32("rtcp-socket", rtcpSocket); + msg->post(); +} + +static void bumpSocketBufferSize(int s) { + int size = 256 * 1024; + CHECK_EQ(setsockopt(s, SOL_SOCKET, SO_RCVBUF, &size, sizeof(size)), 0); +} + +// static +void ARTPConnection::MakePortPair( + int *rtpSocket, int *rtcpSocket, unsigned *rtpPort) { + *rtpSocket = socket(AF_INET, SOCK_DGRAM, 0); + CHECK_GE(*rtpSocket, 0); + + bumpSocketBufferSize(*rtpSocket); + + *rtcpSocket = socket(AF_INET, SOCK_DGRAM, 0); + CHECK_GE(*rtcpSocket, 0); + + bumpSocketBufferSize(*rtcpSocket); + + unsigned start = (rand() * 1000)/ RAND_MAX + 15550; + start &= ~1; + + for (unsigned port = start; port < 65536; port += 2) { + struct sockaddr_in addr; + memset(addr.sin_zero, 0, sizeof(addr.sin_zero)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = htonl(INADDR_ANY); + addr.sin_port = htons(port); + + if (bind(*rtpSocket, + (const struct sockaddr *)&addr, sizeof(addr)) < 0) { + continue; + } + + addr.sin_port = htons(port + 1); + + if (bind(*rtcpSocket, + (const struct sockaddr *)&addr, sizeof(addr)) == 0) { + *rtpPort = port; + return; + } + } + + TRESPASS(); +} + +void ARTPConnection::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatAddStream: + { + onAddStream(msg); + break; + } + + case kWhatRemoveStream: + { + onRemoveStream(msg); + break; + } + + case kWhatPollStreams: + { + onPollStreams(); + break; + } + + case kWhatInjectPacket: + { + onInjectPacket(msg); + break; + } + + default: + { + TRESPASS(); + break; + } + } +} + +void ARTPConnection::onAddStream(const sp<AMessage> &msg) { + mStreams.push_back(StreamInfo()); + StreamInfo *info = &*--mStreams.end(); + + int32_t s; + CHECK(msg->findInt32("rtp-socket", &s)); + info->mRTPSocket = s; + CHECK(msg->findInt32("rtcp-socket", &s)); + info->mRTCPSocket = s; + + int32_t injected; + CHECK(msg->findInt32("injected", &injected)); + + info->mIsInjected = injected; + + sp<RefBase> obj; + CHECK(msg->findObject("session-desc", &obj)); + info->mSessionDesc = static_cast<ASessionDescription *>(obj.get()); + + CHECK(msg->findSize("index", &info->mIndex)); + CHECK(msg->findMessage("notify", &info->mNotifyMsg)); + + info->mNumRTCPPacketsReceived = 0; + info->mNumRTPPacketsReceived = 0; + memset(&info->mRemoteRTCPAddr, 0, sizeof(info->mRemoteRTCPAddr)); + + if (!injected) { + postPollEvent(); + } +} + +void ARTPConnection::onRemoveStream(const sp<AMessage> &msg) { + int32_t rtpSocket, rtcpSocket; + CHECK(msg->findInt32("rtp-socket", &rtpSocket)); + CHECK(msg->findInt32("rtcp-socket", &rtcpSocket)); + + List<StreamInfo>::iterator it = mStreams.begin(); + while (it != mStreams.end() + && (it->mRTPSocket != rtpSocket || it->mRTCPSocket != rtcpSocket)) { + ++it; + } + + if (it == mStreams.end()) { + return; + } + + mStreams.erase(it); +} + +void ARTPConnection::postPollEvent() { + if (mPollEventPending) { + return; + } + + sp<AMessage> msg = new AMessage(kWhatPollStreams, id()); + msg->post(); + + mPollEventPending = true; +} + +void ARTPConnection::onPollStreams() { + mPollEventPending = false; + + if (mStreams.empty()) { + return; + } + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = kSelectTimeoutUs; + + fd_set rs; + FD_ZERO(&rs); + + int maxSocket = -1; + for (List<StreamInfo>::iterator it = mStreams.begin(); + it != mStreams.end(); ++it) { + if ((*it).mIsInjected) { + continue; + } + + FD_SET(it->mRTPSocket, &rs); + FD_SET(it->mRTCPSocket, &rs); + + if (it->mRTPSocket > maxSocket) { + maxSocket = it->mRTPSocket; + } + if (it->mRTCPSocket > maxSocket) { + maxSocket = it->mRTCPSocket; + } + } + + if (maxSocket == -1) { + return; + } + + int res = select(maxSocket + 1, &rs, NULL, NULL, &tv); + + if (res > 0) { + List<StreamInfo>::iterator it = mStreams.begin(); + while (it != mStreams.end()) { + if ((*it).mIsInjected) { + ++it; + continue; + } + + status_t err = OK; + if (FD_ISSET(it->mRTPSocket, &rs)) { + err = receive(&*it, true); + } + if (err == OK && FD_ISSET(it->mRTCPSocket, &rs)) { + err = receive(&*it, false); + } + + if (err == -ECONNRESET) { + // socket failure, this stream is dead, Jim. + + LOGW("failed to receive RTP/RTCP datagram."); + it = mStreams.erase(it); + continue; + } + + ++it; + } + } + + int64_t nowUs = ALooper::GetNowUs(); + if (mLastReceiverReportTimeUs <= 0 + || mLastReceiverReportTimeUs + 5000000ll <= nowUs) { + sp<ABuffer> buffer = new ABuffer(kMaxUDPSize); + List<StreamInfo>::iterator it = mStreams.begin(); + while (it != mStreams.end()) { + StreamInfo *s = &*it; + + if (s->mIsInjected) { + ++it; + continue; + } + + if (s->mNumRTCPPacketsReceived == 0) { + // We have never received any RTCP packets on this stream, + // we don't even know where to send a report. + ++it; + continue; + } + + buffer->setRange(0, 0); + + for (size_t i = 0; i < s->mSources.size(); ++i) { + sp<ARTPSource> source = s->mSources.valueAt(i); + + source->addReceiverReport(buffer); + + if (mFlags & kRegularlyRequestFIR) { + source->addFIR(buffer); + } + } + + if (buffer->size() > 0) { + LOGV("Sending RR..."); + + ssize_t n; + do { + n = sendto( + s->mRTCPSocket, buffer->data(), buffer->size(), 0, + (const struct sockaddr *)&s->mRemoteRTCPAddr, + sizeof(s->mRemoteRTCPAddr)); + } while (n < 0 && errno == EINTR); + + if (n <= 0) { + LOGW("failed to send RTCP receiver report (%s).", + n == 0 ? "connection gone" : strerror(errno)); + + it = mStreams.erase(it); + continue; + } + + CHECK_EQ(n, (ssize_t)buffer->size()); + + mLastReceiverReportTimeUs = nowUs; + } + + ++it; + } + } + + if (!mStreams.empty()) { + postPollEvent(); + } +} + +status_t ARTPConnection::receive(StreamInfo *s, bool receiveRTP) { + LOGV("receiving %s", receiveRTP ? "RTP" : "RTCP"); + + CHECK(!s->mIsInjected); + + sp<ABuffer> buffer = new ABuffer(65536); + + socklen_t remoteAddrLen = + (!receiveRTP && s->mNumRTCPPacketsReceived == 0) + ? sizeof(s->mRemoteRTCPAddr) : 0; + + ssize_t nbytes; + do { + nbytes = recvfrom( + receiveRTP ? s->mRTPSocket : s->mRTCPSocket, + buffer->data(), + buffer->capacity(), + 0, + remoteAddrLen > 0 ? (struct sockaddr *)&s->mRemoteRTCPAddr : NULL, + remoteAddrLen > 0 ? &remoteAddrLen : NULL); + } while (nbytes < 0 && errno == EINTR); + + if (nbytes <= 0) { + return -ECONNRESET; + } + + buffer->setRange(0, nbytes); + + // LOGI("received %d bytes.", buffer->size()); + + status_t err; + if (receiveRTP) { + err = parseRTP(s, buffer); + } else { + err = parseRTCP(s, buffer); + } + + return err; +} + +status_t ARTPConnection::parseRTP(StreamInfo *s, const sp<ABuffer> &buffer) { + if (s->mNumRTPPacketsReceived++ == 0) { + sp<AMessage> notify = s->mNotifyMsg->dup(); + notify->setInt32("first-rtp", true); + notify->post(); + } + + size_t size = buffer->size(); + + if (size < 12) { + // Too short to be a valid RTP header. + return -1; + } + + const uint8_t *data = buffer->data(); + + if ((data[0] >> 6) != 2) { + // Unsupported version. + return -1; + } + + if (data[0] & 0x20) { + // Padding present. + + size_t paddingLength = data[size - 1]; + + if (paddingLength + 12 > size) { + // If we removed this much padding we'd end up with something + // that's too short to be a valid RTP header. + return -1; + } + + size -= paddingLength; + } + + int numCSRCs = data[0] & 0x0f; + + size_t payloadOffset = 12 + 4 * numCSRCs; + + if (size < payloadOffset) { + // Not enough data to fit the basic header and all the CSRC entries. + return -1; + } + + if (data[0] & 0x10) { + // Header eXtension present. + + if (size < payloadOffset + 4) { + // Not enough data to fit the basic header, all CSRC entries + // and the first 4 bytes of the extension header. + + return -1; + } + + const uint8_t *extensionData = &data[payloadOffset]; + + size_t extensionLength = + 4 * (extensionData[2] << 8 | extensionData[3]); + + if (size < payloadOffset + 4 + extensionLength) { + return -1; + } + + payloadOffset += 4 + extensionLength; + } + + uint32_t srcId = u32at(&data[8]); + + sp<ARTPSource> source = findSource(s, srcId); + + uint32_t rtpTime = u32at(&data[4]); + + sp<AMessage> meta = buffer->meta(); + meta->setInt32("ssrc", srcId); + meta->setInt32("rtp-time", rtpTime); + meta->setInt32("PT", data[1] & 0x7f); + meta->setInt32("M", data[1] >> 7); + + buffer->setInt32Data(u16at(&data[2])); + buffer->setRange(payloadOffset, size - payloadOffset); + + source->processRTPPacket(buffer); + + return OK; +} + +status_t ARTPConnection::parseRTCP(StreamInfo *s, const sp<ABuffer> &buffer) { + if (s->mNumRTCPPacketsReceived++ == 0) { + sp<AMessage> notify = s->mNotifyMsg->dup(); + notify->setInt32("first-rtcp", true); + notify->post(); + } + + const uint8_t *data = buffer->data(); + size_t size = buffer->size(); + + while (size > 0) { + if (size < 8) { + // Too short to be a valid RTCP header + return -1; + } + + if ((data[0] >> 6) != 2) { + // Unsupported version. + return -1; + } + + if (data[0] & 0x20) { + // Padding present. + + size_t paddingLength = data[size - 1]; + + if (paddingLength + 12 > size) { + // If we removed this much padding we'd end up with something + // that's too short to be a valid RTP header. + return -1; + } + + size -= paddingLength; + } + + size_t headerLength = 4 * (data[2] << 8 | data[3]) + 4; + + if (size < headerLength) { + // Only received a partial packet? + return -1; + } + + switch (data[1]) { + case 200: + { + parseSR(s, data, headerLength); + break; + } + + case 201: // RR + case 202: // SDES + case 204: // APP + break; + + case 205: // TSFB (transport layer specific feedback) + case 206: // PSFB (payload specific feedback) + // hexdump(data, headerLength); + break; + + case 203: + { + parseBYE(s, data, headerLength); + break; + } + + default: + { + LOGW("Unknown RTCP packet type %u of size %d", + (unsigned)data[1], headerLength); + break; + } + } + + data += headerLength; + size -= headerLength; + } + + return OK; +} + +status_t ARTPConnection::parseBYE( + StreamInfo *s, const uint8_t *data, size_t size) { + size_t SC = data[0] & 0x3f; + + if (SC == 0 || size < (4 + SC * 4)) { + // Packet too short for the minimal BYE header. + return -1; + } + + uint32_t id = u32at(&data[4]); + + sp<ARTPSource> source = findSource(s, id); + + source->byeReceived(); + + return OK; +} + +status_t ARTPConnection::parseSR( + StreamInfo *s, const uint8_t *data, size_t size) { + size_t RC = data[0] & 0x1f; + + if (size < (7 + RC * 6) * 4) { + // Packet too short for the minimal SR header. + return -1; + } + + uint32_t id = u32at(&data[4]); + uint64_t ntpTime = u64at(&data[8]); + uint32_t rtpTime = u32at(&data[16]); + +#if 0 + LOGI("XXX timeUpdate: ssrc=0x%08x, rtpTime %u == ntpTime %.3f", + id, + rtpTime, + (ntpTime >> 32) + (double)(ntpTime & 0xffffffff) / (1ll << 32)); +#endif + + sp<ARTPSource> source = findSource(s, id); + + source->timeUpdate(rtpTime, ntpTime); + + return 0; +} + +sp<ARTPSource> ARTPConnection::findSource(StreamInfo *info, uint32_t srcId) { + sp<ARTPSource> source; + ssize_t index = info->mSources.indexOfKey(srcId); + if (index < 0) { + index = info->mSources.size(); + + source = new ARTPSource( + srcId, info->mSessionDesc, info->mIndex, info->mNotifyMsg); + + info->mSources.add(srcId, source); + } else { + source = info->mSources.valueAt(index); + } + + return source; +} + +void ARTPConnection::injectPacket(int index, const sp<ABuffer> &buffer) { + sp<AMessage> msg = new AMessage(kWhatInjectPacket, id()); + msg->setInt32("index", index); + msg->setObject("buffer", buffer); + msg->post(); +} + +void ARTPConnection::onInjectPacket(const sp<AMessage> &msg) { + int32_t index; + CHECK(msg->findInt32("index", &index)); + + sp<RefBase> obj; + CHECK(msg->findObject("buffer", &obj)); + + sp<ABuffer> buffer = static_cast<ABuffer *>(obj.get()); + + List<StreamInfo>::iterator it = mStreams.begin(); + while (it != mStreams.end() + && it->mRTPSocket != index && it->mRTCPSocket != index) { + ++it; + } + + if (it == mStreams.end()) { + TRESPASS(); + } + + StreamInfo *s = &*it; + + status_t err; + if (it->mRTPSocket == index) { + err = parseRTP(s, buffer); + } else { + err = parseRTCP(s, buffer); + } +} + +} // namespace android +
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTPConnection.h @@ -0,0 +1,97 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_RTP_CONNECTION_H_ + +#define A_RTP_CONNECTION_H_ + +#include <media/stagefright/foundation/AHandler.h> +#include <utils/List.h> + +namespace android { + +struct ABuffer; +struct ARTPSource; +struct ASessionDescription; + +struct ARTPConnection : public AHandler { + enum Flags { + kRegularlyRequestFIR = 2, + }; + + ARTPConnection(uint32_t flags = 0); + + void addStream( + int rtpSocket, int rtcpSocket, + const sp<ASessionDescription> &sessionDesc, size_t index, + const sp<AMessage> ¬ify, + bool injected); + + void removeStream(int rtpSocket, int rtcpSocket); + + void injectPacket(int index, const sp<ABuffer> &buffer); + + // Creates a pair of UDP datagram sockets bound to adjacent ports + // (the rtpSocket is bound to an even port, the rtcpSocket to the + // next higher port). + static void MakePortPair( + int *rtpSocket, int *rtcpSocket, unsigned *rtpPort); + +protected: + virtual ~ARTPConnection(); + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum { + kWhatAddStream, + kWhatRemoveStream, + kWhatPollStreams, + kWhatInjectPacket, + }; + + static const int64_t kSelectTimeoutUs; + + uint32_t mFlags; + + struct StreamInfo; + List<StreamInfo> mStreams; + + bool mPollEventPending; + int64_t mLastReceiverReportTimeUs; + + void onAddStream(const sp<AMessage> &msg); + void onRemoveStream(const sp<AMessage> &msg); + void onPollStreams(); + void onInjectPacket(const sp<AMessage> &msg); + void onSendReceiverReports(); + + status_t receive(StreamInfo *info, bool receiveRTP); + + status_t parseRTP(StreamInfo *info, const sp<ABuffer> &buffer); + status_t parseRTCP(StreamInfo *info, const sp<ABuffer> &buffer); + status_t parseSR(StreamInfo *info, const uint8_t *data, size_t size); + status_t parseBYE(StreamInfo *info, const uint8_t *data, size_t size); + + sp<ARTPSource> findSource(StreamInfo *info, uint32_t id); + + void postPollEvent(); + + DISALLOW_EVIL_CONSTRUCTORS(ARTPConnection); +}; + +} // namespace android + +#endif // A_RTP_CONNECTION_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTPSession.cpp @@ -0,0 +1,244 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ARTPSession" +#include <utils/Log.h> + +#include "ARTPSession.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> + +#include <ctype.h> +#include <arpa/inet.h> +#include <sys/socket.h> + +#include "APacketSource.h" +#include "ARTPConnection.h" +#include "ASessionDescription.h" + +namespace android { + +ARTPSession::ARTPSession() + : mInitCheck(NO_INIT) { +} + +status_t ARTPSession::setup(const sp<ASessionDescription> &desc) { + CHECK_EQ(mInitCheck, (status_t)NO_INIT); + + mDesc = desc; + + mRTPConn = new ARTPConnection(ARTPConnection::kRegularlyRequestFIR); + + looper()->registerHandler(mRTPConn); + + for (size_t i = 1; i < mDesc->countTracks(); ++i) { + AString connection; + if (!mDesc->findAttribute(i, "c=", &connection)) { + // No per-stream connection information, try global fallback. + if (!mDesc->findAttribute(0, "c=", &connection)) { + LOGE("Unable to find connection attribute."); + return mInitCheck; + } + } + if (!(connection == "IN IP4 127.0.0.1")) { + LOGE("We only support localhost connections for now."); + return mInitCheck; + } + + unsigned port; + if (!validateMediaFormat(i, &port) || (port & 1) != 0) { + LOGE("Invalid media format."); + return mInitCheck; + } + + sp<APacketSource> source = new APacketSource(mDesc, i); + if (source->initCheck() != OK) { + LOGE("Unsupported format."); + return mInitCheck; + } + + int rtpSocket = MakeUDPSocket(port); + int rtcpSocket = MakeUDPSocket(port + 1); + + mTracks.push(TrackInfo()); + TrackInfo *info = &mTracks.editItemAt(mTracks.size() - 1); + info->mRTPSocket = rtpSocket; + info->mRTCPSocket = rtcpSocket; + + sp<AMessage> notify = new AMessage(kWhatAccessUnitComplete, id()); + notify->setSize("track-index", mTracks.size() - 1); + + mRTPConn->addStream( + rtpSocket, rtcpSocket, mDesc, i, notify, false /* injected */); + + info->mPacketSource = source; + } + + mInitCheck = OK; + + return OK; +} + +// static +int ARTPSession::MakeUDPSocket(unsigned port) { + int s = socket(AF_INET, SOCK_DGRAM, 0); + CHECK_GE(s, 0); + + struct sockaddr_in addr; + memset(addr.sin_zero, 0, sizeof(addr.sin_zero)); + addr.sin_family = AF_INET; + addr.sin_addr.s_addr = INADDR_ANY; + addr.sin_port = htons(port); + + CHECK_EQ(0, bind(s, (const struct sockaddr *)&addr, sizeof(addr))); + + return s; +} + +ARTPSession::~ARTPSession() { + for (size_t i = 0; i < mTracks.size(); ++i) { + TrackInfo *info = &mTracks.editItemAt(i); + + info->mPacketSource->signalEOS(UNKNOWN_ERROR); + + close(info->mRTPSocket); + close(info->mRTCPSocket); + } +} + +void ARTPSession::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatAccessUnitComplete: + { + int32_t firstRTCP; + if (msg->findInt32("first-rtcp", &firstRTCP)) { + // There won't be an access unit here, it's just a notification + // that the data communication worked since we got the first + // rtcp packet. + break; + } + + size_t trackIndex; + CHECK(msg->findSize("track-index", &trackIndex)); + + int32_t eos; + if (msg->findInt32("eos", &eos) && eos) { + TrackInfo *info = &mTracks.editItemAt(trackIndex); + info->mPacketSource->signalEOS(ERROR_END_OF_STREAM); + break; + } + + sp<RefBase> obj; + CHECK(msg->findObject("access-unit", &obj)); + + sp<ABuffer> accessUnit = static_cast<ABuffer *>(obj.get()); + + uint64_t ntpTime; + CHECK(accessUnit->meta()->findInt64( + "ntp-time", (int64_t *)&ntpTime)); + +#if 0 +#if 0 + printf("access unit complete size=%d\tntp-time=0x%016llx\n", + accessUnit->size(), ntpTime); +#else + LOGI("access unit complete, size=%d, ntp-time=%llu", + accessUnit->size(), ntpTime); + hexdump(accessUnit->data(), accessUnit->size()); +#endif +#endif + +#if 0 + CHECK_GE(accessUnit->size(), 5u); + CHECK(!memcmp("\x00\x00\x00\x01", accessUnit->data(), 4)); + unsigned x = accessUnit->data()[4]; + + LOGI("access unit complete: nalType=0x%02x, nalRefIdc=0x%02x", + x & 0x1f, (x & 0x60) >> 5); +#endif + + accessUnit->meta()->setInt64("ntp-time", ntpTime); + accessUnit->meta()->setInt64("timeUs", 0); + +#if 0 + int32_t damaged; + if (accessUnit->meta()->findInt32("damaged", &damaged) + && damaged != 0) { + LOGI("ignoring damaged AU"); + } else +#endif + { + TrackInfo *info = &mTracks.editItemAt(trackIndex); + info->mPacketSource->queueAccessUnit(accessUnit); + } + break; + } + + default: + TRESPASS(); + break; + } +} + +bool ARTPSession::validateMediaFormat(size_t index, unsigned *port) const { + AString format; + mDesc->getFormat(index, &format); + + ssize_t i = format.find(" "); + if (i < 0) { + return false; + } + + ++i; + size_t j = i; + while (isdigit(format.c_str()[j])) { + ++j; + } + if (format.c_str()[j] != ' ') { + return false; + } + + AString portString(format, i, j - i); + + char *end; + unsigned long x = strtoul(portString.c_str(), &end, 10); + if (end == portString.c_str() || *end != '\0') { + return false; + } + + if (x == 0 || x > 65535) { + return false; + } + + *port = x; + + return true; +} + +size_t ARTPSession::countTracks() { + return mTracks.size(); +} + +sp<MediaSource> ARTPSession::trackAt(size_t index) { + CHECK_LT(index, mTracks.size()); + return mTracks.editItemAt(index).mPacketSource; +} + +} // namespace android
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTPSession.h @@ -0,0 +1,69 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_RTP_SESSION_H_ + +#define A_RTP_SESSION_H_ + +#include <media/stagefright/foundation/AHandler.h> + +namespace android { + +struct APacketSource; +struct ARTPConnection; +struct ASessionDescription; +struct MediaSource; + +struct ARTPSession : public AHandler { + ARTPSession(); + + status_t setup(const sp<ASessionDescription> &desc); + + size_t countTracks(); + sp<MediaSource> trackAt(size_t index); + +protected: + virtual void onMessageReceived(const sp<AMessage> &msg); + + virtual ~ARTPSession(); + +private: + enum { + kWhatAccessUnitComplete = 'accu' + }; + + struct TrackInfo { + int mRTPSocket; + int mRTCPSocket; + + sp<APacketSource> mPacketSource; + }; + + status_t mInitCheck; + sp<ASessionDescription> mDesc; + sp<ARTPConnection> mRTPConn; + + Vector<TrackInfo> mTracks; + + bool validateMediaFormat(size_t index, unsigned *port) const; + static int MakeUDPSocket(unsigned port); + + DISALLOW_EVIL_CONSTRUCTORS(ARTPSession); +}; + +} // namespace android + +#endif // A_RTP_SESSION_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTPSource.cpp @@ -0,0 +1,275 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ARTPSource" +#include <utils/Log.h> + +#include "ARTPSource.h" + +#include "AAMRAssembler.h" +#include "AAVCAssembler.h" +#include "AH263Assembler.h" +#include "AMPEG4AudioAssembler.h" +#include "AMPEG4ElementaryAssembler.h" +#include "ARawAudioAssembler.h" +#include "ASessionDescription.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> + +namespace android { + +static const uint32_t kSourceID = 0xdeadbeef; + +ARTPSource::ARTPSource( + uint32_t id, + const sp<ASessionDescription> &sessionDesc, size_t index, + const sp<AMessage> ¬ify) + : mID(id), + mHighestSeqNumber(0), + mNumBuffersReceived(0), + mLastNTPTime(0), + mLastNTPTimeUpdateUs(0), + mIssueFIRRequests(false), + mLastFIRRequestUs(-1), + mNextFIRSeqNo((rand() * 256.0) / RAND_MAX), + mNotify(notify) { + unsigned long PT; + AString desc; + AString params; + sessionDesc->getFormatType(index, &PT, &desc, ¶ms); + + if (!strncmp(desc.c_str(), "H264/", 5)) { + mAssembler = new AAVCAssembler(notify); + mIssueFIRRequests = true; + } else if (!strncmp(desc.c_str(), "MP4A-LATM/", 10)) { + mAssembler = new AMPEG4AudioAssembler(notify, params); + } else if (!strncmp(desc.c_str(), "H263-1998/", 10) + || !strncmp(desc.c_str(), "H263-2000/", 10)) { + mAssembler = new AH263Assembler(notify); + mIssueFIRRequests = true; + } else if (!strncmp(desc.c_str(), "AMR/", 4)) { + mAssembler = new AAMRAssembler(notify, false /* isWide */, params); + } else if (!strncmp(desc.c_str(), "AMR-WB/", 7)) { + mAssembler = new AAMRAssembler(notify, true /* isWide */, params); + } else if (!strncmp(desc.c_str(), "MP4V-ES/", 8) + || !strncasecmp(desc.c_str(), "mpeg4-generic/", 14)) { + mAssembler = new AMPEG4ElementaryAssembler(notify, desc, params); + mIssueFIRRequests = true; + } else if (ARawAudioAssembler::Supports(desc.c_str())) { + mAssembler = new ARawAudioAssembler(notify, desc.c_str(), params); + } else { + TRESPASS(); + } +} + +static uint32_t AbsDiff(uint32_t seq1, uint32_t seq2) { + return seq1 > seq2 ? seq1 - seq2 : seq2 - seq1; +} + +void ARTPSource::processRTPPacket(const sp<ABuffer> &buffer) { + if (queuePacket(buffer) && mAssembler != NULL) { + mAssembler->onPacketReceived(this); + } +} + +void ARTPSource::timeUpdate(uint32_t rtpTime, uint64_t ntpTime) { + mLastNTPTime = ntpTime; + mLastNTPTimeUpdateUs = ALooper::GetNowUs(); + + sp<AMessage> notify = mNotify->dup(); + notify->setInt32("time-update", true); + notify->setInt32("rtp-time", rtpTime); + notify->setInt64("ntp-time", ntpTime); + notify->post(); +} + +bool ARTPSource::queuePacket(const sp<ABuffer> &buffer) { + uint32_t seqNum = (uint32_t)buffer->int32Data(); + + if (mNumBuffersReceived++ == 0) { + mHighestSeqNumber = seqNum; + mQueue.push_back(buffer); + return true; + } + + // Only the lower 16-bit of the sequence numbers are transmitted, + // derive the high-order bits by choosing the candidate closest + // to the highest sequence number (extended to 32 bits) received so far. + + uint32_t seq1 = seqNum | (mHighestSeqNumber & 0xffff0000); + uint32_t seq2 = seqNum | ((mHighestSeqNumber & 0xffff0000) + 0x10000); + uint32_t seq3 = seqNum | ((mHighestSeqNumber & 0xffff0000) - 0x10000); + uint32_t diff1 = AbsDiff(seq1, mHighestSeqNumber); + uint32_t diff2 = AbsDiff(seq2, mHighestSeqNumber); + uint32_t diff3 = AbsDiff(seq3, mHighestSeqNumber); + + if (diff1 < diff2) { + if (diff1 < diff3) { + // diff1 < diff2 ^ diff1 < diff3 + seqNum = seq1; + } else { + // diff3 <= diff1 < diff2 + seqNum = seq3; + } + } else if (diff2 < diff3) { + // diff2 <= diff1 ^ diff2 < diff3 + seqNum = seq2; + } else { + // diff3 <= diff2 <= diff1 + seqNum = seq3; + } + + if (seqNum > mHighestSeqNumber) { + mHighestSeqNumber = seqNum; + } + + buffer->setInt32Data(seqNum); + + List<sp<ABuffer> >::iterator it = mQueue.begin(); + while (it != mQueue.end() && (uint32_t)(*it)->int32Data() < seqNum) { + ++it; + } + + if (it != mQueue.end() && (uint32_t)(*it)->int32Data() == seqNum) { + LOGW("Discarding duplicate buffer"); + return false; + } + + mQueue.insert(it, buffer); + + return true; +} + +void ARTPSource::byeReceived() { + mAssembler->onByeReceived(); +} + +void ARTPSource::addFIR(const sp<ABuffer> &buffer) { + if (!mIssueFIRRequests) { + return; + } + + int64_t nowUs = ALooper::GetNowUs(); + if (mLastFIRRequestUs >= 0 && mLastFIRRequestUs + 5000000ll > nowUs) { + // Send FIR requests at most every 5 secs. + return; + } + + mLastFIRRequestUs = nowUs; + + if (buffer->size() + 20 > buffer->capacity()) { + LOGW("RTCP buffer too small to accomodate FIR."); + return; + } + + uint8_t *data = buffer->data() + buffer->size(); + + data[0] = 0x80 | 4; + data[1] = 206; // PSFB + data[2] = 0; + data[3] = 4; + data[4] = kSourceID >> 24; + data[5] = (kSourceID >> 16) & 0xff; + data[6] = (kSourceID >> 8) & 0xff; + data[7] = kSourceID & 0xff; + + data[8] = 0x00; // SSRC of media source (unused) + data[9] = 0x00; + data[10] = 0x00; + data[11] = 0x00; + + data[12] = mID >> 24; + data[13] = (mID >> 16) & 0xff; + data[14] = (mID >> 8) & 0xff; + data[15] = mID & 0xff; + + data[16] = mNextFIRSeqNo++; // Seq Nr. + + data[17] = 0x00; // Reserved + data[18] = 0x00; + data[19] = 0x00; + + buffer->setRange(buffer->offset(), buffer->size() + 20); + + LOGV("Added FIR request."); +} + +void ARTPSource::addReceiverReport(const sp<ABuffer> &buffer) { + if (buffer->size() + 32 > buffer->capacity()) { + LOGW("RTCP buffer too small to accomodate RR."); + return; + } + + uint8_t *data = buffer->data() + buffer->size(); + + data[0] = 0x80 | 1; + data[1] = 201; // RR + data[2] = 0; + data[3] = 7; + data[4] = kSourceID >> 24; + data[5] = (kSourceID >> 16) & 0xff; + data[6] = (kSourceID >> 8) & 0xff; + data[7] = kSourceID & 0xff; + + data[8] = mID >> 24; + data[9] = (mID >> 16) & 0xff; + data[10] = (mID >> 8) & 0xff; + data[11] = mID & 0xff; + + data[12] = 0x00; // fraction lost + + data[13] = 0x00; // cumulative lost + data[14] = 0x00; + data[15] = 0x00; + + data[16] = mHighestSeqNumber >> 24; + data[17] = (mHighestSeqNumber >> 16) & 0xff; + data[18] = (mHighestSeqNumber >> 8) & 0xff; + data[19] = mHighestSeqNumber & 0xff; + + data[20] = 0x00; // Interarrival jitter + data[21] = 0x00; + data[22] = 0x00; + data[23] = 0x00; + + uint32_t LSR = 0; + uint32_t DLSR = 0; + if (mLastNTPTime != 0) { + LSR = (mLastNTPTime >> 16) & 0xffffffff; + + DLSR = (uint32_t) + ((ALooper::GetNowUs() - mLastNTPTimeUpdateUs) * 65536.0 / 1E6); + } + + data[24] = LSR >> 24; + data[25] = (LSR >> 16) & 0xff; + data[26] = (LSR >> 8) & 0xff; + data[27] = LSR & 0xff; + + data[28] = DLSR >> 24; + data[29] = (DLSR >> 16) & 0xff; + data[30] = (DLSR >> 8) & 0xff; + data[31] = DLSR & 0xff; + + buffer->setRange(buffer->offset(), buffer->size() + 32); +} + +} // namespace android + +
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTPSource.h @@ -0,0 +1,73 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_RTP_SOURCE_H_ + +#define A_RTP_SOURCE_H_ + +#include <stdint.h> + +#include <media/stagefright/foundation/ABase.h> +#include <utils/List.h> +#include <utils/RefBase.h> + +namespace android { + +struct ABuffer; +struct AMessage; +struct ARTPAssembler; +struct ASessionDescription; + +struct ARTPSource : public RefBase { + ARTPSource( + uint32_t id, + const sp<ASessionDescription> &sessionDesc, size_t index, + const sp<AMessage> ¬ify); + + void processRTPPacket(const sp<ABuffer> &buffer); + void timeUpdate(uint32_t rtpTime, uint64_t ntpTime); + void byeReceived(); + + List<sp<ABuffer> > *queue() { return &mQueue; } + + void addReceiverReport(const sp<ABuffer> &buffer); + void addFIR(const sp<ABuffer> &buffer); + +private: + uint32_t mID; + uint32_t mHighestSeqNumber; + int32_t mNumBuffersReceived; + + List<sp<ABuffer> > mQueue; + sp<ARTPAssembler> mAssembler; + + uint64_t mLastNTPTime; + int64_t mLastNTPTimeUpdateUs; + + bool mIssueFIRRequests; + int64_t mLastFIRRequestUs; + uint8_t mNextFIRSeqNo; + + sp<AMessage> mNotify; + + bool queuePacket(const sp<ABuffer> &buffer); + + DISALLOW_EVIL_CONSTRUCTORS(ARTPSource); +}; + +} // namespace android + +#endif // A_RTP_SOURCE_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTPWriter.cpp @@ -0,0 +1,836 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ARTPWriter" +#include <utils/Log.h> + +#include "ARTPWriter.h" + +#include <fcntl.h> + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaBuffer.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MediaSource.h> +#include <media/stagefright/MetaData.h> +#include <utils/ByteOrder.h> + +#define PT 97 +#define PT_STR "97" + +namespace android { + +// static const size_t kMaxPacketSize = 65507; // maximum payload in UDP over IP +static const size_t kMaxPacketSize = 1500; + +static int UniformRand(int limit) { + return ((double)rand() * limit) / RAND_MAX; +} + +ARTPWriter::ARTPWriter(int fd) + : mFlags(0), + mFd(dup(fd)), + mLooper(new ALooper), + mReflector(new AHandlerReflector<ARTPWriter>(this)) { + CHECK_GE(fd, 0); + + mLooper->setName("rtp writer"); + mLooper->registerHandler(mReflector); + mLooper->start(); + + mSocket = socket(AF_INET, SOCK_DGRAM, 0); + CHECK_GE(mSocket, 0); + + memset(mRTPAddr.sin_zero, 0, sizeof(mRTPAddr.sin_zero)); + mRTPAddr.sin_family = AF_INET; + +#if 1 + mRTPAddr.sin_addr.s_addr = INADDR_ANY; +#else + mRTPAddr.sin_addr.s_addr = inet_addr("172.19.18.246"); +#endif + + mRTPAddr.sin_port = htons(5634); + CHECK_EQ(0, ntohs(mRTPAddr.sin_port) & 1); + + mRTCPAddr = mRTPAddr; + mRTCPAddr.sin_port = htons(ntohs(mRTPAddr.sin_port) | 1); + +#if LOG_TO_FILES + mRTPFd = open( + "/data/misc/rtpout.bin", + O_WRONLY | O_CREAT | O_TRUNC, + 0644); + CHECK_GE(mRTPFd, 0); + + mRTCPFd = open( + "/data/misc/rtcpout.bin", + O_WRONLY | O_CREAT | O_TRUNC, + 0644); + CHECK_GE(mRTCPFd, 0); +#endif +} + +ARTPWriter::~ARTPWriter() { +#if LOG_TO_FILES + close(mRTCPFd); + mRTCPFd = -1; + + close(mRTPFd); + mRTPFd = -1; +#endif + + close(mSocket); + mSocket = -1; + + close(mFd); + mFd = -1; +} + +status_t ARTPWriter::addSource(const sp<MediaSource> &source) { + mSource = source; + return OK; +} + +bool ARTPWriter::reachedEOS() { + Mutex::Autolock autoLock(mLock); + return (mFlags & kFlagEOS) != 0; +} + +status_t ARTPWriter::start(MetaData *params) { + Mutex::Autolock autoLock(mLock); + if (mFlags & kFlagStarted) { + return INVALID_OPERATION; + } + + mFlags &= ~kFlagEOS; + mSourceID = rand(); + mSeqNo = UniformRand(65536); + mRTPTimeBase = rand(); + mNumRTPSent = 0; + mNumRTPOctetsSent = 0; + mLastRTPTime = 0; + mLastNTPTime = 0; + mNumSRsSent = 0; + + const char *mime; + CHECK(mSource->getFormat()->findCString(kKeyMIMEType, &mime)); + + mMode = INVALID; + if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_AVC)) { + mMode = H264; + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_VIDEO_H263)) { + mMode = H263; + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_NB)) { + mMode = AMR_NB; + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AMR_WB)) { + mMode = AMR_WB; + } else { + TRESPASS(); + } + + (new AMessage(kWhatStart, mReflector->id()))->post(); + + while (!(mFlags & kFlagStarted)) { + mCondition.wait(mLock); + } + + return OK; +} + +status_t ARTPWriter::stop() { + Mutex::Autolock autoLock(mLock); + if (!(mFlags & kFlagStarted)) { + return OK; + } + + (new AMessage(kWhatStop, mReflector->id()))->post(); + + while (mFlags & kFlagStarted) { + mCondition.wait(mLock); + } + return OK; +} + +status_t ARTPWriter::pause() { + return OK; +} + +static void StripStartcode(MediaBuffer *buffer) { + if (buffer->range_length() < 4) { + return; + } + + const uint8_t *ptr = + (const uint8_t *)buffer->data() + buffer->range_offset(); + + if (!memcmp(ptr, "\x00\x00\x00\x01", 4)) { + buffer->set_range( + buffer->range_offset() + 4, buffer->range_length() - 4); + } +} + +void ARTPWriter::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatStart: + { + CHECK_EQ(mSource->start(), (status_t)OK); + +#if 0 + if (mMode == H264) { + MediaBuffer *buffer; + CHECK_EQ(mSource->read(&buffer), (status_t)OK); + + StripStartcode(buffer); + makeH264SPropParamSets(buffer); + buffer->release(); + buffer = NULL; + } + + dumpSessionDesc(); +#endif + + { + Mutex::Autolock autoLock(mLock); + mFlags |= kFlagStarted; + mCondition.signal(); + } + + (new AMessage(kWhatRead, mReflector->id()))->post(); + (new AMessage(kWhatSendSR, mReflector->id()))->post(); + break; + } + + case kWhatStop: + { + CHECK_EQ(mSource->stop(), (status_t)OK); + + sendBye(); + + { + Mutex::Autolock autoLock(mLock); + mFlags &= ~kFlagStarted; + mCondition.signal(); + } + break; + } + + case kWhatRead: + { + { + Mutex::Autolock autoLock(mLock); + if (!(mFlags & kFlagStarted)) { + break; + } + } + + onRead(msg); + break; + } + + case kWhatSendSR: + { + { + Mutex::Autolock autoLock(mLock); + if (!(mFlags & kFlagStarted)) { + break; + } + } + + onSendSR(msg); + break; + } + + default: + TRESPASS(); + break; + } +} + +void ARTPWriter::onRead(const sp<AMessage> &msg) { + MediaBuffer *mediaBuf; + status_t err = mSource->read(&mediaBuf); + + if (err != OK) { + LOGI("reached EOS."); + + Mutex::Autolock autoLock(mLock); + mFlags |= kFlagEOS; + return; + } + + if (mediaBuf->range_length() > 0) { + LOGV("read buffer of size %d", mediaBuf->range_length()); + + if (mMode == H264) { + StripStartcode(mediaBuf); + sendAVCData(mediaBuf); + } else if (mMode == H263) { + sendH263Data(mediaBuf); + } else if (mMode == AMR_NB || mMode == AMR_WB) { + sendAMRData(mediaBuf); + } + } + + mediaBuf->release(); + mediaBuf = NULL; + + msg->post(); +} + +void ARTPWriter::onSendSR(const sp<AMessage> &msg) { + sp<ABuffer> buffer = new ABuffer(65536); + buffer->setRange(0, 0); + + addSR(buffer); + addSDES(buffer); + + send(buffer, true /* isRTCP */); + + ++mNumSRsSent; + msg->post(3000000); +} + +void ARTPWriter::send(const sp<ABuffer> &buffer, bool isRTCP) { + ssize_t n = sendto( + mSocket, buffer->data(), buffer->size(), 0, + (const struct sockaddr *)(isRTCP ? &mRTCPAddr : &mRTPAddr), + sizeof(mRTCPAddr)); + + CHECK_EQ(n, (ssize_t)buffer->size()); + +#if LOG_TO_FILES + int fd = isRTCP ? mRTCPFd : mRTPFd; + + uint32_t ms = tolel(ALooper::GetNowUs() / 1000ll); + uint32_t length = tolel(buffer->size()); + write(fd, &ms, sizeof(ms)); + write(fd, &length, sizeof(length)); + write(fd, buffer->data(), buffer->size()); +#endif +} + +void ARTPWriter::addSR(const sp<ABuffer> &buffer) { + uint8_t *data = buffer->data() + buffer->size(); + + data[0] = 0x80 | 0; + data[1] = 200; // SR + data[2] = 0; + data[3] = 6; + data[4] = mSourceID >> 24; + data[5] = (mSourceID >> 16) & 0xff; + data[6] = (mSourceID >> 8) & 0xff; + data[7] = mSourceID & 0xff; + + data[8] = mLastNTPTime >> (64 - 8); + data[9] = (mLastNTPTime >> (64 - 16)) & 0xff; + data[10] = (mLastNTPTime >> (64 - 24)) & 0xff; + data[11] = (mLastNTPTime >> 32) & 0xff; + data[12] = (mLastNTPTime >> 24) & 0xff; + data[13] = (mLastNTPTime >> 16) & 0xff; + data[14] = (mLastNTPTime >> 8) & 0xff; + data[15] = mLastNTPTime & 0xff; + + data[16] = (mLastRTPTime >> 24) & 0xff; + data[17] = (mLastRTPTime >> 16) & 0xff; + data[18] = (mLastRTPTime >> 8) & 0xff; + data[19] = mLastRTPTime & 0xff; + + data[20] = mNumRTPSent >> 24; + data[21] = (mNumRTPSent >> 16) & 0xff; + data[22] = (mNumRTPSent >> 8) & 0xff; + data[23] = mNumRTPSent & 0xff; + + data[24] = mNumRTPOctetsSent >> 24; + data[25] = (mNumRTPOctetsSent >> 16) & 0xff; + data[26] = (mNumRTPOctetsSent >> 8) & 0xff; + data[27] = mNumRTPOctetsSent & 0xff; + + buffer->setRange(buffer->offset(), buffer->size() + 28); +} + +void ARTPWriter::addSDES(const sp<ABuffer> &buffer) { + uint8_t *data = buffer->data() + buffer->size(); + data[0] = 0x80 | 1; + data[1] = 202; // SDES + data[4] = mSourceID >> 24; + data[5] = (mSourceID >> 16) & 0xff; + data[6] = (mSourceID >> 8) & 0xff; + data[7] = mSourceID & 0xff; + + size_t offset = 8; + + data[offset++] = 1; // CNAME + + static const char *kCNAME = "someone@somewhere"; + data[offset++] = strlen(kCNAME); + + memcpy(&data[offset], kCNAME, strlen(kCNAME)); + offset += strlen(kCNAME); + + data[offset++] = 7; // NOTE + + static const char *kNOTE = "Hell's frozen over."; + data[offset++] = strlen(kNOTE); + + memcpy(&data[offset], kNOTE, strlen(kNOTE)); + offset += strlen(kNOTE); + + data[offset++] = 0; + + if ((offset % 4) > 0) { + size_t count = 4 - (offset % 4); + switch (count) { + case 3: + data[offset++] = 0; + case 2: + data[offset++] = 0; + case 1: + data[offset++] = 0; + } + } + + size_t numWords = (offset / 4) - 1; + data[2] = numWords >> 8; + data[3] = numWords & 0xff; + + buffer->setRange(buffer->offset(), buffer->size() + offset); +} + +// static +uint64_t ARTPWriter::GetNowNTP() { + uint64_t nowUs = ALooper::GetNowUs(); + + nowUs += ((70ll * 365 + 17) * 24) * 60 * 60 * 1000000ll; + + uint64_t hi = nowUs / 1000000ll; + uint64_t lo = ((1ll << 32) * (nowUs % 1000000ll)) / 1000000ll; + + return (hi << 32) | lo; +} + +void ARTPWriter::dumpSessionDesc() { + AString sdp; + sdp = "v=0\r\n"; + + sdp.append("o=- "); + + uint64_t ntp = GetNowNTP(); + sdp.append(ntp); + sdp.append(" "); + sdp.append(ntp); + sdp.append(" IN IP4 127.0.0.0\r\n"); + + sdp.append( + "s=Sample\r\n" + "i=Playing around\r\n" + "c=IN IP4 "); + + struct in_addr addr; + addr.s_addr = ntohl(INADDR_LOOPBACK); + + sdp.append(inet_ntoa(addr)); + + sdp.append( + "\r\n" + "t=0 0\r\n" + "a=range:npt=now-\r\n"); + + sp<MetaData> meta = mSource->getFormat(); + + if (mMode == H264 || mMode == H263) { + sdp.append("m=video "); + } else { + sdp.append("m=audio "); + } + + sdp.append(StringPrintf("%d", ntohs(mRTPAddr.sin_port))); + sdp.append( + " RTP/AVP " PT_STR "\r\n" + "b=AS 320000\r\n" + "a=rtpmap:" PT_STR " "); + + if (mMode == H264) { + sdp.append("H264/90000"); + } else if (mMode == H263) { + sdp.append("H263-1998/90000"); + } else if (mMode == AMR_NB || mMode == AMR_WB) { + int32_t sampleRate, numChannels; + CHECK(mSource->getFormat()->findInt32(kKeySampleRate, &sampleRate)); + CHECK(mSource->getFormat()->findInt32(kKeyChannelCount, &numChannels)); + + CHECK_EQ(numChannels, 1); + CHECK_EQ(sampleRate, (mMode == AMR_NB) ? 8000 : 16000); + + sdp.append(mMode == AMR_NB ? "AMR" : "AMR-WB"); + sdp.append(StringPrintf("/%d/%d", sampleRate, numChannels)); + } else { + TRESPASS(); + } + + sdp.append("\r\n"); + + if (mMode == H264 || mMode == H263) { + int32_t width, height; + CHECK(meta->findInt32(kKeyWidth, &width)); + CHECK(meta->findInt32(kKeyHeight, &height)); + + sdp.append("a=cliprect 0,0,"); + sdp.append(height); + sdp.append(","); + sdp.append(width); + sdp.append("\r\n"); + + sdp.append( + "a=framesize:" PT_STR " "); + sdp.append(width); + sdp.append("-"); + sdp.append(height); + sdp.append("\r\n"); + } + + if (mMode == H264) { + sdp.append( + "a=fmtp:" PT_STR " profile-level-id="); + sdp.append(mProfileLevel); + sdp.append(";sprop-parameter-sets="); + + sdp.append(mSeqParamSet); + sdp.append(","); + sdp.append(mPicParamSet); + sdp.append(";packetization-mode=1\r\n"); + } else if (mMode == AMR_NB || mMode == AMR_WB) { + sdp.append("a=fmtp:" PT_STR " octed-align\r\n"); + } + + LOGI("%s", sdp.c_str()); +} + +void ARTPWriter::makeH264SPropParamSets(MediaBuffer *buffer) { + static const char kStartCode[] = "\x00\x00\x00\x01"; + + const uint8_t *data = + (const uint8_t *)buffer->data() + buffer->range_offset(); + size_t size = buffer->range_length(); + + CHECK_GE(size, 0u); + + size_t startCodePos = 0; + while (startCodePos + 3 < size + && memcmp(kStartCode, &data[startCodePos], 4)) { + ++startCodePos; + } + + CHECK_LT(startCodePos + 3, size); + + CHECK_EQ((unsigned)data[0], 0x67u); + + mProfileLevel = + StringPrintf("%02X%02X%02X", data[1], data[2], data[3]); + + encodeBase64(data, startCodePos, &mSeqParamSet); + + encodeBase64(&data[startCodePos + 4], size - startCodePos - 4, + &mPicParamSet); +} + +void ARTPWriter::sendBye() { + sp<ABuffer> buffer = new ABuffer(8); + uint8_t *data = buffer->data(); + *data++ = (2 << 6) | 1; + *data++ = 203; + *data++ = 0; + *data++ = 1; + *data++ = mSourceID >> 24; + *data++ = (mSourceID >> 16) & 0xff; + *data++ = (mSourceID >> 8) & 0xff; + *data++ = mSourceID & 0xff; + buffer->setRange(0, 8); + + send(buffer, true /* isRTCP */); +} + +void ARTPWriter::sendAVCData(MediaBuffer *mediaBuf) { + // 12 bytes RTP header + 2 bytes for the FU-indicator and FU-header. + CHECK_GE(kMaxPacketSize, 12u + 2u); + + int64_t timeUs; + CHECK(mediaBuf->meta_data()->findInt64(kKeyTime, &timeUs)); + + uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100ll); + + const uint8_t *mediaData = + (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset(); + + sp<ABuffer> buffer = new ABuffer(kMaxPacketSize); + if (mediaBuf->range_length() + 12 <= buffer->capacity()) { + // The data fits into a single packet + uint8_t *data = buffer->data(); + data[0] = 0x80; + data[1] = (1 << 7) | PT; // M-bit + data[2] = (mSeqNo >> 8) & 0xff; + data[3] = mSeqNo & 0xff; + data[4] = rtpTime >> 24; + data[5] = (rtpTime >> 16) & 0xff; + data[6] = (rtpTime >> 8) & 0xff; + data[7] = rtpTime & 0xff; + data[8] = mSourceID >> 24; + data[9] = (mSourceID >> 16) & 0xff; + data[10] = (mSourceID >> 8) & 0xff; + data[11] = mSourceID & 0xff; + + memcpy(&data[12], + mediaData, mediaBuf->range_length()); + + buffer->setRange(0, mediaBuf->range_length() + 12); + + send(buffer, false /* isRTCP */); + + ++mSeqNo; + ++mNumRTPSent; + mNumRTPOctetsSent += buffer->size() - 12; + } else { + // FU-A + + unsigned nalType = mediaData[0]; + size_t offset = 1; + + bool firstPacket = true; + while (offset < mediaBuf->range_length()) { + size_t size = mediaBuf->range_length() - offset; + bool lastPacket = true; + if (size + 12 + 2 > buffer->capacity()) { + lastPacket = false; + size = buffer->capacity() - 12 - 2; + } + + uint8_t *data = buffer->data(); + data[0] = 0x80; + data[1] = (lastPacket ? (1 << 7) : 0x00) | PT; // M-bit + data[2] = (mSeqNo >> 8) & 0xff; + data[3] = mSeqNo & 0xff; + data[4] = rtpTime >> 24; + data[5] = (rtpTime >> 16) & 0xff; + data[6] = (rtpTime >> 8) & 0xff; + data[7] = rtpTime & 0xff; + data[8] = mSourceID >> 24; + data[9] = (mSourceID >> 16) & 0xff; + data[10] = (mSourceID >> 8) & 0xff; + data[11] = mSourceID & 0xff; + + data[12] = 28 | (nalType & 0xe0); + + CHECK(!firstPacket || !lastPacket); + + data[13] = + (firstPacket ? 0x80 : 0x00) + | (lastPacket ? 0x40 : 0x00) + | (nalType & 0x1f); + + memcpy(&data[14], &mediaData[offset], size); + + buffer->setRange(0, 14 + size); + + send(buffer, false /* isRTCP */); + + ++mSeqNo; + ++mNumRTPSent; + mNumRTPOctetsSent += buffer->size() - 12; + + firstPacket = false; + offset += size; + } + } + + mLastRTPTime = rtpTime; + mLastNTPTime = GetNowNTP(); +} + +void ARTPWriter::sendH263Data(MediaBuffer *mediaBuf) { + CHECK_GE(kMaxPacketSize, 12u + 2u); + + int64_t timeUs; + CHECK(mediaBuf->meta_data()->findInt64(kKeyTime, &timeUs)); + + uint32_t rtpTime = mRTPTimeBase + (timeUs * 9 / 100ll); + + const uint8_t *mediaData = + (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset(); + + // hexdump(mediaData, mediaBuf->range_length()); + + CHECK_EQ((unsigned)mediaData[0], 0u); + CHECK_EQ((unsigned)mediaData[1], 0u); + + size_t offset = 2; + size_t size = mediaBuf->range_length(); + + while (offset < size) { + sp<ABuffer> buffer = new ABuffer(kMaxPacketSize); + // CHECK_LE(mediaBuf->range_length() -2 + 14, buffer->capacity()); + + size_t remaining = size - offset; + bool lastPacket = (remaining + 14 <= buffer->capacity()); + if (!lastPacket) { + remaining = buffer->capacity() - 14; + } + + uint8_t *data = buffer->data(); + data[0] = 0x80; + data[1] = (lastPacket ? 0x80 : 0x00) | PT; // M-bit + data[2] = (mSeqNo >> 8) & 0xff; + data[3] = mSeqNo & 0xff; + data[4] = rtpTime >> 24; + data[5] = (rtpTime >> 16) & 0xff; + data[6] = (rtpTime >> 8) & 0xff; + data[7] = rtpTime & 0xff; + data[8] = mSourceID >> 24; + data[9] = (mSourceID >> 16) & 0xff; + data[10] = (mSourceID >> 8) & 0xff; + data[11] = mSourceID & 0xff; + + data[12] = (offset == 2) ? 0x04 : 0x00; // P=?, V=0 + data[13] = 0x00; // PLEN = PEBIT = 0 + + memcpy(&data[14], &mediaData[offset], remaining); + offset += remaining; + + buffer->setRange(0, remaining + 14); + + send(buffer, false /* isRTCP */); + + ++mSeqNo; + ++mNumRTPSent; + mNumRTPOctetsSent += buffer->size() - 12; + } + + mLastRTPTime = rtpTime; + mLastNTPTime = GetNowNTP(); +} + +static size_t getFrameSize(bool isWide, unsigned FT) { + static const size_t kFrameSizeNB[8] = { + 95, 103, 118, 134, 148, 159, 204, 244 + }; + static const size_t kFrameSizeWB[9] = { + 132, 177, 253, 285, 317, 365, 397, 461, 477 + }; + + size_t frameSize = isWide ? kFrameSizeWB[FT] : kFrameSizeNB[FT]; + + // Round up bits to bytes and add 1 for the header byte. + frameSize = (frameSize + 7) / 8 + 1; + + return frameSize; +} + +void ARTPWriter::sendAMRData(MediaBuffer *mediaBuf) { + const uint8_t *mediaData = + (const uint8_t *)mediaBuf->data() + mediaBuf->range_offset(); + + size_t mediaLength = mediaBuf->range_length(); + + CHECK_GE(kMaxPacketSize, 12u + 1u + mediaLength); + + const bool isWide = (mMode == AMR_WB); + + int64_t timeUs; + CHECK(mediaBuf->meta_data()->findInt64(kKeyTime, &timeUs)); + uint32_t rtpTime = mRTPTimeBase + (timeUs / (isWide ? 250 : 125)); + + // hexdump(mediaData, mediaLength); + + Vector<uint8_t> tableOfContents; + size_t srcOffset = 0; + while (srcOffset < mediaLength) { + uint8_t toc = mediaData[srcOffset]; + + unsigned FT = (toc >> 3) & 0x0f; + CHECK((isWide && FT <= 8) || (!isWide && FT <= 7)); + + tableOfContents.push(toc); + srcOffset += getFrameSize(isWide, FT); + } + CHECK_EQ(srcOffset, mediaLength); + + sp<ABuffer> buffer = new ABuffer(kMaxPacketSize); + CHECK_LE(mediaLength + 12 + 1, buffer->capacity()); + + // The data fits into a single packet + uint8_t *data = buffer->data(); + data[0] = 0x80; + data[1] = PT; + if (mNumRTPSent == 0) { + // Signal start of talk-spurt. + data[1] |= 0x80; // M-bit + } + data[2] = (mSeqNo >> 8) & 0xff; + data[3] = mSeqNo & 0xff; + data[4] = rtpTime >> 24; + data[5] = (rtpTime >> 16) & 0xff; + data[6] = (rtpTime >> 8) & 0xff; + data[7] = rtpTime & 0xff; + data[8] = mSourceID >> 24; + data[9] = (mSourceID >> 16) & 0xff; + data[10] = (mSourceID >> 8) & 0xff; + data[11] = mSourceID & 0xff; + + data[12] = 0xf0; // CMR=15, RR=0 + + size_t dstOffset = 13; + + for (size_t i = 0; i < tableOfContents.size(); ++i) { + uint8_t toc = tableOfContents[i]; + + if (i + 1 < tableOfContents.size()) { + toc |= 0x80; + } else { + toc &= ~0x80; + } + + data[dstOffset++] = toc; + } + + srcOffset = 0; + for (size_t i = 0; i < tableOfContents.size(); ++i) { + uint8_t toc = tableOfContents[i]; + unsigned FT = (toc >> 3) & 0x0f; + size_t frameSize = getFrameSize(isWide, FT); + + ++srcOffset; // skip toc + memcpy(&data[dstOffset], &mediaData[srcOffset], frameSize - 1); + srcOffset += frameSize - 1; + dstOffset += frameSize - 1; + } + + buffer->setRange(0, dstOffset); + + send(buffer, false /* isRTCP */); + + ++mSeqNo; + ++mNumRTPSent; + mNumRTPOctetsSent += buffer->size() - 12; + + mLastRTPTime = rtpTime; + mLastNTPTime = GetNowNTP(); +} + +} // namespace android +
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTPWriter.h @@ -0,0 +1,128 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_RTP_WRITER_H_ + +#define A_RTP_WRITER_H_ + +#include <media/stagefright/foundation/ABase.h> +#include <media/stagefright/foundation/AHandlerReflector.h> +#include <media/stagefright/foundation/AString.h> +#include <media/stagefright/foundation/base64.h> +#include <media/stagefright/MediaWriter.h> + +#include <arpa/inet.h> +#include <sys/socket.h> + +#define LOG_TO_FILES 0 + +namespace android { + +struct ABuffer; +struct MediaBuffer; + +struct ARTPWriter : public MediaWriter { + ARTPWriter(int fd); + + virtual status_t addSource(const sp<MediaSource> &source); + virtual bool reachedEOS(); + virtual status_t start(MetaData *params); + virtual status_t stop(); + virtual status_t pause(); + + virtual void onMessageReceived(const sp<AMessage> &msg); + +protected: + virtual ~ARTPWriter(); + +private: + enum { + kWhatStart = 'strt', + kWhatStop = 'stop', + kWhatRead = 'read', + kWhatSendSR = 'sr ', + }; + + enum { + kFlagStarted = 1, + kFlagEOS = 2, + }; + + Mutex mLock; + Condition mCondition; + uint32_t mFlags; + + int mFd; + +#if LOG_TO_FILES + int mRTPFd; + int mRTCPFd; +#endif + + sp<MediaSource> mSource; + sp<ALooper> mLooper; + sp<AHandlerReflector<ARTPWriter> > mReflector; + + int mSocket; + struct sockaddr_in mRTPAddr; + struct sockaddr_in mRTCPAddr; + + AString mProfileLevel; + AString mSeqParamSet; + AString mPicParamSet; + + uint32_t mSourceID; + uint32_t mSeqNo; + uint32_t mRTPTimeBase; + uint32_t mNumRTPSent; + uint32_t mNumRTPOctetsSent; + uint32_t mLastRTPTime; + uint64_t mLastNTPTime; + + int32_t mNumSRsSent; + + enum { + INVALID, + H264, + H263, + AMR_NB, + AMR_WB, + } mMode; + + static uint64_t GetNowNTP(); + + void onRead(const sp<AMessage> &msg); + void onSendSR(const sp<AMessage> &msg); + + void addSR(const sp<ABuffer> &buffer); + void addSDES(const sp<ABuffer> &buffer); + + void makeH264SPropParamSets(MediaBuffer *buffer); + void dumpSessionDesc(); + + void sendBye(); + void sendAVCData(MediaBuffer *mediaBuf); + void sendH263Data(MediaBuffer *mediaBuf); + void sendAMRData(MediaBuffer *mediaBuf); + + void send(const sp<ABuffer> &buffer, bool isRTCP); + + DISALLOW_EVIL_CONSTRUCTORS(ARTPWriter); +}; + +} // namespace android + +#endif // A_RTP_WRITER_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTSPConnection.cpp @@ -0,0 +1,1082 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ARTSPConnection" +#include <utils/Log.h> + +#include "ARTSPConnection.h" + +#include <cutils/properties.h> + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/base64.h> +#include <media/stagefright/MediaErrors.h> + +#include <arpa/inet.h> +#include <fcntl.h> +#include <netdb.h> +#include <sys/socket.h> + +#include "HTTPBase.h" + +#include "nsCOMPtr.h" +#include "nsString.h" +#include "nsNetCID.h" +#include "nsIServiceManager.h" +#include "nsICryptoHash.h" + +namespace android { + +// static +const int64_t ARTSPConnection::kSelectTimeoutUs = 1000ll; + +ARTSPConnection::ARTSPConnection(bool uidValid, uid_t uid) + : mUIDValid(uidValid), + mUID(uid), + mState(DISCONNECTED), + mAuthType(NONE), + mSocket(-1), + mConnectionID(0), + mNextCSeq(0), + mReceiveResponseEventPending(false) { + MakeUserAgent(&mUserAgent); +} + +ARTSPConnection::~ARTSPConnection() { + if (mSocket >= 0) { + LOGE("Connection is still open, closing the socket."); + if (mUIDValid) { + HTTPBase::UnRegisterSocketUserTag(mSocket); + } + close(mSocket); + mSocket = -1; + } +} + +void ARTSPConnection::connect(const char *url, const sp<AMessage> &reply) { + sp<AMessage> msg = new AMessage(kWhatConnect, id()); + msg->setString("url", url); + msg->setMessage("reply", reply); + msg->post(); +} + +void ARTSPConnection::disconnect(const sp<AMessage> &reply) { + sp<AMessage> msg = new AMessage(kWhatDisconnect, id()); + msg->setMessage("reply", reply); + msg->post(); +} + +void ARTSPConnection::sendRequest( + const char *request, const sp<AMessage> &reply) { + sp<AMessage> msg = new AMessage(kWhatSendRequest, id()); + msg->setString("request", request); + msg->setMessage("reply", reply); + msg->post(); +} + +void ARTSPConnection::observeBinaryData(const sp<AMessage> &reply) { + sp<AMessage> msg = new AMessage(kWhatObserveBinaryData, id()); + msg->setMessage("reply", reply); + msg->post(); +} + +void ARTSPConnection::onMessageReceived(const sp<AMessage> &msg) { + switch (msg->what()) { + case kWhatConnect: + onConnect(msg); + break; + + case kWhatDisconnect: + onDisconnect(msg); + break; + + case kWhatCompleteConnection: + onCompleteConnection(msg); + break; + + case kWhatSendRequest: + onSendRequest(msg); + break; + + case kWhatReceiveResponse: + onReceiveResponse(); + break; + + case kWhatObserveBinaryData: + { + CHECK(msg->findMessage("reply", &mObserveBinaryMessage)); + break; + } + + default: + TRESPASS(); + break; + } +} + +// static +bool ARTSPConnection::ParseURL( + const char *url, AString *host, unsigned *port, AString *path, + AString *user, AString *pass) { + host->clear(); + *port = 0; + path->clear(); + user->clear(); + pass->clear(); + + if (strncasecmp("rtsp://", url, 7)) { + return false; + } + + const char *slashPos = strchr(&url[7], '/'); + + if (slashPos == NULL) { + host->setTo(&url[7]); + path->setTo("/"); + } else { + host->setTo(&url[7], slashPos - &url[7]); + path->setTo(slashPos); + } + + ssize_t atPos = host->find("@"); + + if (atPos >= 0) { + // Split of user:pass@ from hostname. + + AString userPass(*host, 0, atPos); + host->erase(0, atPos + 1); + + ssize_t colonPos = userPass.find(":"); + + if (colonPos < 0) { + *user = userPass; + } else { + user->setTo(userPass, 0, colonPos); + pass->setTo(userPass, colonPos + 1, userPass.size() - colonPos - 1); + } + } + + const char *colonPos = strchr(host->c_str(), ':'); + + if (colonPos != NULL) { + unsigned long x; + if (!ParseSingleUnsignedLong(colonPos + 1, &x) || x >= 65536) { + return false; + } + + *port = x; + + size_t colonOffset = colonPos - host->c_str(); + size_t trailing = host->size() - colonOffset; + host->erase(colonOffset, trailing); + } else { + *port = 554; + } + + return true; +} + +static status_t MakeSocketBlocking(int s, bool blocking) { + // Make socket non-blocking. + int flags = fcntl(s, F_GETFL, 0); + + if (flags == -1) { + return UNKNOWN_ERROR; + } + + if (blocking) { + flags &= ~O_NONBLOCK; + } else { + flags |= O_NONBLOCK; + } + + flags = fcntl(s, F_SETFL, flags); + + return flags == -1 ? UNKNOWN_ERROR : OK; +} + +void ARTSPConnection::onConnect(const sp<AMessage> &msg) { + ++mConnectionID; + + if (mState != DISCONNECTED) { + if (mUIDValid) { + HTTPBase::UnRegisterSocketUserTag(mSocket); + } + close(mSocket); + mSocket = -1; + + flushPendingRequests(); + } + + mState = CONNECTING; + + AString url; + CHECK(msg->findString("url", &url)); + + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + AString host, path; + unsigned port; + if (!ParseURL(url.c_str(), &host, &port, &path, &mUser, &mPass) + || (mUser.size() > 0 && mPass.size() == 0)) { + // If we have a user name but no password we have to give up + // right here, since we currently have no way of asking the user + // for this information. + + LOGE("Malformed rtsp url %s", url.c_str()); + + reply->setInt32("result", ERROR_MALFORMED); + reply->post(); + + mState = DISCONNECTED; + return; + } + + if (mUser.size() > 0) { + LOGV("user = '%s', pass = '%s'", mUser.c_str(), mPass.c_str()); + } + + struct hostent *ent = gethostbyname(host.c_str()); + if (ent == NULL) { + LOGE("Unknown host %s", host.c_str()); + + reply->setInt32("result", -ENOENT); + reply->post(); + + mState = DISCONNECTED; + return; + } + + mSocket = socket(AF_INET, SOCK_STREAM, 0); + + if (mUIDValid) { + HTTPBase::RegisterSocketUserTag(mSocket, mUID, + (uint32_t)*(uint32_t*) "RTSP"); + } + + MakeSocketBlocking(mSocket, false); + + struct sockaddr_in remote; + memset(remote.sin_zero, 0, sizeof(remote.sin_zero)); + remote.sin_family = AF_INET; + remote.sin_addr.s_addr = *(in_addr_t *)ent->h_addr; + remote.sin_port = htons(port); + + int err = ::connect( + mSocket, (const struct sockaddr *)&remote, sizeof(remote)); + + reply->setInt32("server-ip", ntohl(remote.sin_addr.s_addr)); + + if (err < 0) { + if (errno == EINPROGRESS) { + sp<AMessage> msg = new AMessage(kWhatCompleteConnection, id()); + msg->setMessage("reply", reply); + msg->setInt32("connection-id", mConnectionID); + msg->post(); + return; + } + + reply->setInt32("result", -errno); + mState = DISCONNECTED; + + if (mUIDValid) { + HTTPBase::UnRegisterSocketUserTag(mSocket); + } + close(mSocket); + mSocket = -1; + } else { + reply->setInt32("result", OK); + mState = CONNECTED; + mNextCSeq = 1; + + postReceiveReponseEvent(); + } + + reply->post(); +} + +void ARTSPConnection::performDisconnect() { + if (mUIDValid) { + HTTPBase::UnRegisterSocketUserTag(mSocket); + } + close(mSocket); + mSocket = -1; + + flushPendingRequests(); + + mUser.clear(); + mPass.clear(); + mAuthType = NONE; + mNonce.clear(); + + mState = DISCONNECTED; +} + +void ARTSPConnection::onDisconnect(const sp<AMessage> &msg) { + if (mState == CONNECTED || mState == CONNECTING) { + performDisconnect(); + } + + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + reply->setInt32("result", OK); + + reply->post(); +} + +void ARTSPConnection::onCompleteConnection(const sp<AMessage> &msg) { + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + int32_t connectionID; + CHECK(msg->findInt32("connection-id", &connectionID)); + + if ((connectionID != mConnectionID) || mState != CONNECTING) { + // While we were attempting to connect, the attempt was + // cancelled. + reply->setInt32("result", -ECONNABORTED); + reply->post(); + return; + } + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = kSelectTimeoutUs; + + fd_set ws; + FD_ZERO(&ws); + FD_SET(mSocket, &ws); + + int res = select(mSocket + 1, NULL, &ws, NULL, &tv); + CHECK_GE(res, 0); + + if (res == 0) { + // Timed out. Not yet connected. + + msg->post(); + return; + } + + int err; + socklen_t optionLen = sizeof(err); + CHECK_EQ(getsockopt(mSocket, SOL_SOCKET, SO_ERROR, &err, &optionLen), 0); + CHECK_EQ(optionLen, (socklen_t)sizeof(err)); + + if (err != 0) { + LOGE("err = %d (%s)", err, strerror(err)); + + reply->setInt32("result", -err); + + mState = DISCONNECTED; + if (mUIDValid) { + HTTPBase::UnRegisterSocketUserTag(mSocket); + } + close(mSocket); + mSocket = -1; + } else { + reply->setInt32("result", OK); + mState = CONNECTED; + mNextCSeq = 1; + + postReceiveReponseEvent(); + } + + reply->post(); +} + +void ARTSPConnection::onSendRequest(const sp<AMessage> &msg) { + sp<AMessage> reply; + CHECK(msg->findMessage("reply", &reply)); + + if (mState != CONNECTED) { + reply->setInt32("result", -ENOTCONN); + reply->post(); + return; + } + + AString request; + CHECK(msg->findString("request", &request)); + + // Just in case we need to re-issue the request with proper authentication + // later, stash it away. + reply->setString("original-request", request.c_str(), request.size()); + + addAuthentication(&request); + addUserAgent(&request); + + // Find the boundary between headers and the body. + ssize_t i = request.find("\r\n\r\n"); + CHECK_GE(i, 0); + + int32_t cseq = mNextCSeq++; + + AString cseqHeader = "CSeq: "; + cseqHeader.append(cseq); + cseqHeader.append("\r\n"); + + request.insert(cseqHeader, i + 2); + + LOGV("request: '%s'", request.c_str()); + + size_t numBytesSent = 0; + while (numBytesSent < request.size()) { + ssize_t n = + send(mSocket, request.c_str() + numBytesSent, + request.size() - numBytesSent, 0); + + if (n < 0 && errno == EINTR) { + continue; + } + + if (n <= 0) { + performDisconnect(); + + if (n == 0) { + // Server closed the connection. + LOGE("Server unexpectedly closed the connection."); + + reply->setInt32("result", ERROR_IO); + reply->post(); + } else { + LOGE("Error sending rtsp request. (%s)", strerror(errno)); + reply->setInt32("result", -errno); + reply->post(); + } + + return; + } + + numBytesSent += (size_t)n; + } + + mPendingRequests.add(cseq, reply); +} + +void ARTSPConnection::onReceiveResponse() { + mReceiveResponseEventPending = false; + + if (mState != CONNECTED) { + return; + } + + struct timeval tv; + tv.tv_sec = 0; + tv.tv_usec = kSelectTimeoutUs; + + fd_set rs; + FD_ZERO(&rs); + FD_SET(mSocket, &rs); + + int res = select(mSocket + 1, &rs, NULL, NULL, &tv); + CHECK_GE(res, 0); + + if (res == 1) { + MakeSocketBlocking(mSocket, true); + + bool success = receiveRTSPReponse(); + + MakeSocketBlocking(mSocket, false); + + if (!success) { + // Something horrible, irreparable has happened. + flushPendingRequests(); + return; + } + } + + postReceiveReponseEvent(); +} + +void ARTSPConnection::flushPendingRequests() { + for (size_t i = 0; i < mPendingRequests.size(); ++i) { + sp<AMessage> reply = mPendingRequests.valueAt(i); + + reply->setInt32("result", -ECONNABORTED); + reply->post(); + } + + mPendingRequests.clear(); +} + +void ARTSPConnection::postReceiveReponseEvent() { + if (mReceiveResponseEventPending) { + return; + } + + sp<AMessage> msg = new AMessage(kWhatReceiveResponse, id()); + msg->post(); + + mReceiveResponseEventPending = true; +} + +status_t ARTSPConnection::receive(void *data, size_t size) { + size_t offset = 0; + while (offset < size) { + ssize_t n = recv(mSocket, (uint8_t *)data + offset, size - offset, 0); + + if (n < 0 && errno == EINTR) { + continue; + } + + if (n <= 0) { + performDisconnect(); + + if (n == 0) { + // Server closed the connection. + LOGE("Server unexpectedly closed the connection."); + return ERROR_IO; + } else { + LOGE("Error reading rtsp response. (%s)", strerror(errno)); + return -errno; + } + } + + offset += (size_t)n; + } + + return OK; +} + +bool ARTSPConnection::receiveLine(AString *line) { + line->clear(); + + bool sawCR = false; + for (;;) { + char c; + if (receive(&c, 1) != OK) { + return false; + } + + if (sawCR && c == '\n') { + line->erase(line->size() - 1, 1); + return true; + } + + line->append(&c, 1); + + if (c == '$' && line->size() == 1) { + // Special-case for interleaved binary data. + return true; + } + + sawCR = (c == '\r'); + } +} + +sp<ABuffer> ARTSPConnection::receiveBinaryData() { + uint8_t x[3]; + if (receive(x, 3) != OK) { + return NULL; + } + + sp<ABuffer> buffer = new ABuffer((x[1] << 8) | x[2]); + if (receive(buffer->data(), buffer->size()) != OK) { + return NULL; + } + + buffer->meta()->setInt32("index", (int32_t)x[0]); + + return buffer; +} + +static bool IsRTSPVersion(const AString &s) { + return s == "RTSP/1.0"; +} + +bool ARTSPConnection::receiveRTSPReponse() { + AString statusLine; + + if (!receiveLine(&statusLine)) { + return false; + } + + if (statusLine == "$") { + sp<ABuffer> buffer = receiveBinaryData(); + + if (buffer == NULL) { + return false; + } + + if (mObserveBinaryMessage != NULL) { + sp<AMessage> notify = mObserveBinaryMessage->dup(); + notify->setObject("buffer", buffer); + notify->post(); + } else { + LOGW("received binary data, but no one cares."); + } + + return true; + } + + sp<ARTSPResponse> response = new ARTSPResponse; + response->mStatusLine = statusLine; + + LOGI("status: %s", response->mStatusLine.c_str()); + + ssize_t space1 = response->mStatusLine.find(" "); + if (space1 < 0) { + return false; + } + ssize_t space2 = response->mStatusLine.find(" ", space1 + 1); + if (space2 < 0) { + return false; + } + + bool isRequest = false; + + if (!IsRTSPVersion(AString(response->mStatusLine, 0, space1))) { + CHECK(IsRTSPVersion( + AString( + response->mStatusLine, + space2 + 1, + response->mStatusLine.size() - space2 - 1))); + + isRequest = true; + + response->mStatusCode = 0; + } else { + AString statusCodeStr( + response->mStatusLine, space1 + 1, space2 - space1 - 1); + + if (!ParseSingleUnsignedLong( + statusCodeStr.c_str(), &response->mStatusCode) + || response->mStatusCode < 100 || response->mStatusCode > 999) { + return false; + } + } + + AString line; + ssize_t lastDictIndex = -1; + for (;;) { + if (!receiveLine(&line)) { + break; + } + + if (line.empty()) { + break; + } + + LOGV("line: '%s'", line.c_str()); + + if (line.c_str()[0] == ' ' || line.c_str()[0] == '\t') { + // Support for folded header values. + + if (lastDictIndex < 0) { + // First line cannot be a continuation of the previous one. + return false; + } + + AString &value = response->mHeaders.editValueAt(lastDictIndex); + value.append(line); + + continue; + } + + ssize_t colonPos = line.find(":"); + if (colonPos < 0) { + // Malformed header line. + return false; + } + + AString key(line, 0, colonPos); + key.trim(); + key.tolower(); + + line.erase(0, colonPos + 1); + + lastDictIndex = response->mHeaders.add(key, line); + } + + for (size_t i = 0; i < response->mHeaders.size(); ++i) { + response->mHeaders.editValueAt(i).trim(); + } + + unsigned long contentLength = 0; + + ssize_t i = response->mHeaders.indexOfKey("content-length"); + + if (i >= 0) { + AString value = response->mHeaders.valueAt(i); + if (!ParseSingleUnsignedLong(value.c_str(), &contentLength)) { + return false; + } + } + + if (contentLength > 0) { + response->mContent = new ABuffer(contentLength); + + if (receive(response->mContent->data(), contentLength) != OK) { + return false; + } + } + + if (response->mStatusCode == 401) { + if (mAuthType == NONE && mUser.size() > 0 + && parseAuthMethod(response)) { + ssize_t i; + CHECK_EQ((status_t)OK, findPendingRequest(response, &i)); + CHECK_GE(i, 0); + + sp<AMessage> reply = mPendingRequests.valueAt(i); + mPendingRequests.removeItemsAt(i); + + AString request; + CHECK(reply->findString("original-request", &request)); + + sp<AMessage> msg = new AMessage(kWhatSendRequest, id()); + msg->setMessage("reply", reply); + msg->setString("request", request.c_str(), request.size()); + + LOGI("re-sending request with authentication headers..."); + onSendRequest(msg); + + return true; + } + } + + return isRequest + ? handleServerRequest(response) + : notifyResponseListener(response); +} + +bool ARTSPConnection::handleServerRequest(const sp<ARTSPResponse> &request) { + // Implementation of server->client requests is optional for all methods + // but we do need to respond, even if it's just to say that we don't + // support the method. + + ssize_t space1 = request->mStatusLine.find(" "); + CHECK_GE(space1, 0); + + AString response; + response.append("RTSP/1.0 501 Not Implemented\r\n"); + + ssize_t i = request->mHeaders.indexOfKey("cseq"); + + if (i >= 0) { + AString value = request->mHeaders.valueAt(i); + + unsigned long cseq; + if (!ParseSingleUnsignedLong(value.c_str(), &cseq)) { + return false; + } + + response.append("CSeq: "); + response.append(cseq); + response.append("\r\n"); + } + + response.append("\r\n"); + + size_t numBytesSent = 0; + while (numBytesSent < response.size()) { + ssize_t n = + send(mSocket, response.c_str() + numBytesSent, + response.size() - numBytesSent, 0); + + if (n < 0 && errno == EINTR) { + continue; + } + + if (n <= 0) { + if (n == 0) { + // Server closed the connection. + LOGE("Server unexpectedly closed the connection."); + } else { + LOGE("Error sending rtsp response (%s).", strerror(errno)); + } + + performDisconnect(); + + return false; + } + + numBytesSent += (size_t)n; + } + + return true; +} + +// static +bool ARTSPConnection::ParseSingleUnsignedLong( + const char *from, unsigned long *x) { + char *end; + *x = strtoul(from, &end, 10); + + if (end == from || *end != '\0') { + return false; + } + + return true; +} + +status_t ARTSPConnection::findPendingRequest( + const sp<ARTSPResponse> &response, ssize_t *index) const { + *index = 0; + + ssize_t i = response->mHeaders.indexOfKey("cseq"); + + if (i < 0) { + // This is an unsolicited server->client message. + return OK; + } + + AString value = response->mHeaders.valueAt(i); + + unsigned long cseq; + if (!ParseSingleUnsignedLong(value.c_str(), &cseq)) { + return ERROR_MALFORMED; + } + + i = mPendingRequests.indexOfKey(cseq); + + if (i < 0) { + return -ENOENT; + } + + *index = i; + + return OK; +} + +bool ARTSPConnection::notifyResponseListener( + const sp<ARTSPResponse> &response) { + ssize_t i; + status_t err = findPendingRequest(response, &i); + + if (err == OK && i < 0) { + // An unsolicited server response is not a problem. + return true; + } + + if (err != OK) { + return false; + } + + sp<AMessage> reply = mPendingRequests.valueAt(i); + mPendingRequests.removeItemsAt(i); + + reply->setInt32("result", OK); + reply->setObject("response", response); + reply->post(); + + return true; +} + +bool ARTSPConnection::parseAuthMethod(const sp<ARTSPResponse> &response) { + ssize_t i = response->mHeaders.indexOfKey("www-authenticate"); + + if (i < 0) { + return false; + } + + AString value = response->mHeaders.valueAt(i); + + if (!strncmp(value.c_str(), "Basic", 5)) { + mAuthType = BASIC; + } else { + + CHECK(!strncmp(value.c_str(), "Digest", 6)); + mAuthType = DIGEST; + + i = value.find("nonce="); + CHECK_GE(i, 0); + CHECK_EQ(value.c_str()[i + 6], '\"'); + ssize_t j = value.find("\"", i + 7); + CHECK_GE(j, 0); + + mNonce.setTo(value, i + 7, j - i - 7); + } + + return true; +} + +static void H(const AString &s, AString *out) { + nsresult rv; + nsCOMPtr<nsICryptoHash> cryptoHash; + + out->clear(); + + cryptoHash = do_CreateInstance(NS_CRYPTO_HASH_CONTRACTID, &rv); + if (NS_FAILED(rv)) { + LOGE(("Failed to create crypto hash.")); + return; + } + rv = cryptoHash->Init(nsICryptoHash::MD5); + if (NS_FAILED(rv)) { + LOGE(("Failed to init md5 hash.")); + return; + } + + rv = cryptoHash->Update((unsigned char*)s.c_str(), s.size()); + if (NS_FAILED(rv)) { + LOGE(("Failed to update md5 hash.")); + return; + } + + nsAutoCString hashString; + rv = cryptoHash->Finish(false, hashString); + if (NS_FAILED(rv)) { + LOGE(("Failed to finish md5 hash.")); + return; + } + + const uint8_t kHashKeyLength = 16; + if (hashString.Length() != kHashKeyLength) { + LOGE(("Invalid hash key length.")); + return; + } + + const char *key; + hashString.GetData(&key); + + for (size_t i = 0; i < 16; ++i) { + char nibble = key[i] >> 4; + if (nibble <= 9) { + nibble += '0'; + } else { + nibble += 'a' - 10; + } + out->append(&nibble, 1); + + nibble = key[i] & 0x0f; + if (nibble <= 9) { + nibble += '0'; + } else { + nibble += 'a' - 10; + } + out->append(&nibble, 1); + } +} + +static void GetMethodAndURL( + const AString &request, AString *method, AString *url) { + ssize_t space1 = request.find(" "); + CHECK_GE(space1, 0); + + ssize_t space2 = request.find(" ", space1 + 1); + CHECK_GE(space2, 0); + + method->setTo(request, 0, space1); + url->setTo(request, space1 + 1, space2 - space1); +} + +void ARTSPConnection::addAuthentication(AString *request) { + if (mAuthType == NONE) { + return; + } + + // Find the boundary between headers and the body. + ssize_t i = request->find("\r\n\r\n"); + CHECK_GE(i, 0); + + if (mAuthType == BASIC) { + AString tmp; + tmp.append(mUser); + tmp.append(":"); + tmp.append(mPass); + + AString out; + encodeBase64(tmp.c_str(), tmp.size(), &out); + + AString fragment; + fragment.append("Authorization: Basic "); + fragment.append(out); + fragment.append("\r\n"); + + request->insert(fragment, i + 2); + + return; + } + + CHECK_EQ((int)mAuthType, (int)DIGEST); + + AString method, url; + GetMethodAndURL(*request, &method, &url); + + AString A1; + A1.append(mUser); + A1.append(":"); + A1.append("Streaming Server"); + A1.append(":"); + A1.append(mPass); + + AString A2; + A2.append(method); + A2.append(":"); + A2.append(url); + + AString HA1, HA2; + H(A1, &HA1); + H(A2, &HA2); + + AString tmp; + tmp.append(HA1); + tmp.append(":"); + tmp.append(mNonce); + tmp.append(":"); + tmp.append(HA2); + + AString digest; + H(tmp, &digest); + + AString fragment; + fragment.append("Authorization: Digest "); + fragment.append("nonce=\""); + fragment.append(mNonce); + fragment.append("\", "); + fragment.append("username=\""); + fragment.append(mUser); + fragment.append("\", "); + fragment.append("uri=\""); + fragment.append(url); + fragment.append("\", "); + fragment.append("response=\""); + fragment.append(digest); + fragment.append("\""); + fragment.append("\r\n"); + + request->insert(fragment, i + 2); +} + +// static +void ARTSPConnection::MakeUserAgent(AString *userAgent) { + userAgent->clear(); + userAgent->setTo("User-Agent: stagefright/1.1 (Linux;Android "); + +#if (PROPERTY_VALUE_MAX < 8) +#error "PROPERTY_VALUE_MAX must be at least 8" +#endif + + char value[PROPERTY_VALUE_MAX]; + property_get("ro.build.version.release", value, "Unknown"); + userAgent->append(value); + userAgent->append(")\r\n"); +} + +void ARTSPConnection::addUserAgent(AString *request) const { + // Find the boundary between headers and the body. + ssize_t i = request->find("\r\n\r\n"); + CHECK_GE(i, 0); + + request->insert(mUserAgent, i + 2); +} + +} // namespace android
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARTSPConnection.h @@ -0,0 +1,132 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_RTSP_CONNECTION_H_ + +#define A_RTSP_CONNECTION_H_ + +#include <media/stagefright/foundation/AHandler.h> +#include <media/stagefright/foundation/AString.h> + +namespace android { + +struct ABuffer; + +struct ARTSPResponse : public RefBase { + unsigned long mStatusCode; + AString mStatusLine; + KeyedVector<AString,AString> mHeaders; + sp<ABuffer> mContent; +}; + +struct ARTSPConnection : public AHandler { + ARTSPConnection(bool uidValid = false, uid_t uid = 0); + + void connect(const char *url, const sp<AMessage> &reply); + void disconnect(const sp<AMessage> &reply); + + void sendRequest(const char *request, const sp<AMessage> &reply); + + void observeBinaryData(const sp<AMessage> &reply); + + static bool ParseURL( + const char *url, AString *host, unsigned *port, AString *path, + AString *user, AString *pass); + +protected: + virtual ~ARTSPConnection(); + virtual void onMessageReceived(const sp<AMessage> &msg); + +private: + enum State { + DISCONNECTED, + CONNECTING, + CONNECTED, + }; + + enum { + kWhatConnect = 'conn', + kWhatDisconnect = 'disc', + kWhatCompleteConnection = 'comc', + kWhatSendRequest = 'sreq', + kWhatReceiveResponse = 'rres', + kWhatObserveBinaryData = 'obin', + }; + + enum AuthType { + NONE, + BASIC, + DIGEST + }; + + static const int64_t kSelectTimeoutUs; + + bool mUIDValid; + uid_t mUID; + State mState; + AString mUser, mPass; + AuthType mAuthType; + AString mNonce; + int mSocket; + int32_t mConnectionID; + int32_t mNextCSeq; + bool mReceiveResponseEventPending; + + KeyedVector<int32_t, sp<AMessage> > mPendingRequests; + + sp<AMessage> mObserveBinaryMessage; + + AString mUserAgent; + + void performDisconnect(); + + void onConnect(const sp<AMessage> &msg); + void onDisconnect(const sp<AMessage> &msg); + void onCompleteConnection(const sp<AMessage> &msg); + void onSendRequest(const sp<AMessage> &msg); + void onReceiveResponse(); + + void flushPendingRequests(); + void postReceiveReponseEvent(); + + // Return false iff something went unrecoverably wrong. + bool receiveRTSPReponse(); + status_t receive(void *data, size_t size); + bool receiveLine(AString *line); + sp<ABuffer> receiveBinaryData(); + bool notifyResponseListener(const sp<ARTSPResponse> &response); + + bool parseAuthMethod(const sp<ARTSPResponse> &response); + void addAuthentication(AString *request); + + void addUserAgent(AString *request) const; + + status_t findPendingRequest( + const sp<ARTSPResponse> &response, ssize_t *index) const; + + bool handleServerRequest(const sp<ARTSPResponse> &request); + + static bool ParseSingleUnsignedLong( + const char *from, unsigned long *x); + + static void MakeUserAgent(AString *userAgent); + + DISALLOW_EVIL_CONSTRUCTORS(ARTSPConnection); +}; + +} // namespace android + +#endif // A_RTSP_CONNECTION_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARawAudioAssembler.cpp @@ -0,0 +1,143 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ARawAudioAssembler" +#include <utils/Log.h> + +#include "ARawAudioAssembler.h" + +#include "ARTPSource.h" +#include "ASessionDescription.h" + +#include <media/stagefright/foundation/ABuffer.h> +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AMessage.h> +#include <media/stagefright/foundation/hexdump.h> +#include <media/stagefright/MediaDefs.h> +#include <media/stagefright/MetaData.h> +#include <media/stagefright/Utils.h> + +namespace android { + +ARawAudioAssembler::ARawAudioAssembler( + const sp<AMessage> ¬ify, const char *desc, const AString ¶ms) + : mNotifyMsg(notify), + mNextExpectedSeqNoValid(false), + mNextExpectedSeqNo(0) { +} + +ARawAudioAssembler::~ARawAudioAssembler() { +} + +ARTPAssembler::AssemblyStatus ARawAudioAssembler::assembleMore( + const sp<ARTPSource> &source) { + return addPacket(source); +} + +ARTPAssembler::AssemblyStatus ARawAudioAssembler::addPacket( + const sp<ARTPSource> &source) { + List<sp<ABuffer> > *queue = source->queue(); + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + + if (mNextExpectedSeqNoValid) { + List<sp<ABuffer> >::iterator it = queue->begin(); + while (it != queue->end()) { + if ((uint32_t)(*it)->int32Data() >= mNextExpectedSeqNo) { + break; + } + + it = queue->erase(it); + } + + if (queue->empty()) { + return NOT_ENOUGH_DATA; + } + } + + sp<ABuffer> buffer = *queue->begin(); + + if (!mNextExpectedSeqNoValid) { + mNextExpectedSeqNoValid = true; + mNextExpectedSeqNo = (uint32_t)buffer->int32Data(); + } else if ((uint32_t)buffer->int32Data() != mNextExpectedSeqNo) { + LOGV("Not the sequence number I expected"); + + return WRONG_SEQUENCE_NUMBER; + } + + // hexdump(buffer->data(), buffer->size()); + + if (buffer->size() < 1) { + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + LOGV("raw audio packet too short."); + + return MALFORMED_PACKET; + } + + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setObject("access-unit", buffer); + msg->post(); + + queue->erase(queue->begin()); + ++mNextExpectedSeqNo; + + return OK; +} + +void ARawAudioAssembler::packetLost() { + CHECK(mNextExpectedSeqNoValid); + ++mNextExpectedSeqNo; +} + +void ARawAudioAssembler::onByeReceived() { + sp<AMessage> msg = mNotifyMsg->dup(); + msg->setInt32("eos", true); + msg->post(); +} + +// static +bool ARawAudioAssembler::Supports(const char *desc) { + return !strncmp(desc, "PCMU/", 5) + || !strncmp(desc, "PCMA/", 5); +} + +// static +void ARawAudioAssembler::MakeFormat( + const char *desc, const sp<MetaData> &format) { + if (!strncmp(desc, "PCMU/", 5)) { + format->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_G711_MLAW); + } else if (!strncmp(desc, "PCMA/", 5)) { + format->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_G711_ALAW); + } else { + TRESPASS(); + } + + int32_t sampleRate, numChannels; + ASessionDescription::ParseFormatDesc( + desc, &sampleRate, &numChannels); + + format->setInt32(kKeySampleRate, sampleRate); + format->setInt32(kKeyChannelCount, numChannels); +} + +} // namespace android +
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ARawAudioAssembler.h @@ -0,0 +1,60 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef A_RAW_AUDIO_ASSEMBLER_H_ + +#define A_RAW_AUDIO_ASSEMBLER_H_ + +#include "ARTPAssembler.h" + +namespace android { + +struct AMessage; +struct AString; +struct MetaData; + +struct ARawAudioAssembler : public ARTPAssembler { + ARawAudioAssembler( + const sp<AMessage> ¬ify, + const char *desc, const AString ¶ms); + + static bool Supports(const char *desc); + + static void MakeFormat( + const char *desc, const sp<MetaData> &format); + +protected: + virtual ~ARawAudioAssembler(); + + virtual AssemblyStatus assembleMore(const sp<ARTPSource> &source); + virtual void onByeReceived(); + virtual void packetLost(); + +private: + bool mIsWide; + + sp<AMessage> mNotifyMsg; + bool mNextExpectedSeqNoValid; + uint32_t mNextExpectedSeqNo; + + AssemblyStatus addPacket(const sp<ARTPSource> &source); + + DISALLOW_EVIL_CONSTRUCTORS(ARawAudioAssembler); +}; + +} // namespace android + +#endif // A_RAW_AUDIO_ASSEMBLER_H_
new file mode 100644 --- /dev/null +++ b/netwerk/protocol/rtsp/rtsp/ASessionDescription.cpp @@ -0,0 +1,336 @@ +/* + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "ASessionDescription" +#include <utils/Log.h> + +#include "ASessionDescription.h" + +#include <media/stagefright/foundation/ADebug.h> +#include <media/stagefright/foundation/AString.h> + +#include <stdlib.h> + +namespace android { + +ASessionDescription::ASessionDescription() + : mIsValid(false) { +} + +ASessionDescription::~ASessionDescription() { +} + +bool ASessionDescription::setTo(const void *data, size_t size) { + mIsValid = parse(data, size); + + if (!mIsValid) { + mTracks.clear(); + mFormats.clear(); + } + + return mIsValid; +} + +bool ASessionDescription::parse(const void *data, size_t size) { + mTracks.clear(); + mFormats.clear(); + + mTracks.push(Attribs()); + mFormats.push(AString("[root]")); + + AString desc((const char *)data, size); + + size_t i = 0; + for (;;) { + ssize_t eolPos = desc.find("\n", i); + + if (eolPos < 0) { + break; + } + + AString line; + if ((size_t)eolPos > i && desc.c_str()[eolPos - 1] == '\r') { + // We accept both '\n' and '\r\n' line endings, if it's + // the latter, strip the '\r' as well. + line.setTo(desc, i, eolPos - i - 1); + } else { + line.setTo(desc, i, eolPos - i); + } + + if (line.empty()) { + i = eolPos + 1; + continue; + } + + if (line.size() < 2 || line.c_str()[1] != '=') { + return false; + } + + LOGI("%s", line.c_str()); + + switch (line.c_str()[0]) { + case 'v': + { + if (strcmp(line.c_str(), "v=0")) { + return false; + } + break; + } + + case 'a': + case 'b': + { + AString key, value; + + ssize_t colonPos = line.find(":", 2); + if (colonPos < 0) { + key = line; + } else { + key.setTo(line, 0, colonPos); + + if (key == "a=fmtp" || key == "a=rtpmap" + || key == "a=framesize") { + ssize_t spacePos = line.find(" ", colonPos + 1); + if (spacePos < 0) { + return false; + } + +