summaryrefslogtreecommitdiffstats
path: root/media
diff options
context:
space:
mode:
authorAndreas Huber <andih@google.com>2009-07-14 16:56:47 -0700
committerAndreas Huber <andih@google.com>2009-07-16 09:49:14 -0700
commit20111aa043c5f404472bc63b90bc5aad906b1101 (patch)
treeb8c69d66954b3bf1aee4f12d05deb387e38a9282 /media
parent508a3b61c4bce835f721237ddd40922b61f069d7 (diff)
downloadframeworks_av-20111aa043c5f404472bc63b90bc5aad906b1101.zip
frameworks_av-20111aa043c5f404472bc63b90bc5aad906b1101.tar.gz
frameworks_av-20111aa043c5f404472bc63b90bc5aad906b1101.tar.bz2
Squashed commit of the following:
commit 8d179995654c19d9094e51989dc84ed0deba6e04 Author: Andreas Huber <andih@google.com> Date: Tue Jul 14 16:30:40 2009 -0700 Protect stagefright code with BUILD_WITH_STAGEFRIGHT build-define. commit 99b41acb58c34e8f024c30c833a09fd7b31c4e1f Author: Andreas Huber <andih@google.com> Date: Tue Jul 14 14:06:00 2009 -0700 OMX::list_nodes API, stagefright --list now lists all available OMX components. commit 94a74c5b24aae6d7e446fff83d62ae3a57dc351b Author: Andreas Huber <andih@google.com> Date: Mon Jul 13 16:04:29 2009 -0700 Reenabled "record" commandline application to test encoding, support for h263 codec specific data when writing to .mp4. commit d363dd74f611577567c66d25900d6264c306468c Author: Andreas Huber <andih@google.com> Date: Mon Jul 13 10:59:58 2009 -0700 Make non-socket communication in IOMX the default. commit e89fc82c4fd138c88339887baf72f1d0e67f8660 Author: Andreas Huber <andih@google.com> Date: Mon Jul 13 10:32:33 2009 -0700 The OMX interface now optionally supports communication through the binder only (no sockets involved) commit 28611de11223fba65299787cdd33f7fdbd6a60a3 Author: Andreas Huber <andih@google.com> Date: Fri Jul 10 15:09:13 2009 -0700 Split off the logic to render to a Surface (not ISurface!) into SurfaceRender.{cpp,h} commit c0cfa885faaeb17d3fc5436f6447b61708bd6fe1 Author: Andreas Huber <andih@google.com> Date: Fri Jul 10 12:21:46 2009 -0700 Fix display dimensions vs. decoded dimensions display issues, unfortunately breaks software rendering a bit... commit ae209a2b5d09caed5dd383c910f238926aaa8fc6 Author: Andreas Huber <andih@google.com> Date: Fri Jul 10 09:33:29 2009 -0700 Simple video decoder performance test in "stagefright" commandline tool. commit 59fe7840111d6f46dd3c48f4950146aed962dbd2 Author: Andreas Huber <andih@google.com> Date: Thu Jul 9 16:05:12 2009 -0700 StagefrightPlayer: Properly publish video dimensions by notifying the listener. commit 51b6598f0e47be5e12d77f1d14e111e71c593638 Author: Andreas Huber <andih@google.com> Date: Thu Jul 9 11:38:15 2009 -0700 MediaPlayerInterface::AudioSink now supports the specification of a callback for pull-based audio. commit 31ce53375d748da3e7d30e993ce89dae97fd65ea Author: Andreas Huber <andih@google.com> Date: Wed Jul 8 17:23:37 2009 -0700 Proper support for pushing audio to a specified AudioSink interface. Still not bulletproof: video falls behind frequently. commit a23e312b10631a5d47f5c9831205beac6b84ab36 Author: Andreas Huber <andih@google.com> Date: Wed Jul 8 15:10:13 2009 -0700 IGenesisPlayer and friends are no more, java interface now uses local playback instead of playing inside the media server process. commit 42ee5c06ab5ba1c16f50472b0b26a0526635c73c Author: Andreas Huber <andih@google.com> Date: Wed Jul 8 13:17:03 2009 -0700 MP3 extractor now supports seeking (only fixed bitrate files currently supported) commit 96a790405763bfa738d1c6845c99a9fdf579b66f Author: Andreas Huber <andih@google.com> Date: Tue Jul 7 10:53:54 2009 -0700 Keeping up-to-date with new master-gl changes, removing CameraSource for now, removing unnecessary commandline utilities from the build. commit 6c9dcb7c628c0347154dd7f37913a3a5f04deb99 Author: Andreas Huber <andih@google.com> Date: Mon Jul 6 16:40:02 2009 -0700 Finally take advantage of the fact that the Qcom OMX decoders already place the output buffers into memory accessible by the hardware yuv converter and avoid extra allocations and memcpys in that case. Split of the render logic into subclasses of VideoRenderer. commit e8d46e81cffc15ffb0bdf03f511e4a4497ca7621 Author: Andreas Huber <andih@google.com> Date: Mon Jul 6 12:18:29 2009 -0700 Added a few more cases of valid MPEG4 files to the sniffer, fixed end of stream notifications in MPEG4 files. commit 88bffc5ed4033a607080f8777a1c0ec9e3e079de Author: Andreas Huber <andih@google.com> Date: Thu Jul 2 12:25:03 2009 -0700 MediaPlayerImpl now properly pauses/resumes instead of tearing down the decoder chains and starting back up from the beginning. commit 732aa0a87c7e8d8a10776b275712649954962414 Author: Andreas Huber <andih@google.com> Date: Wed Jul 1 14:58:56 2009 -0700 Remove pthread_mutex_* and pthread_cond_* in favour of Mutex and Condition. commit 669e5944b2fe0542a3b8dba0c441a94503e99fb0 Author: Andreas Huber <andih@google.com> Date: Wed Jul 1 14:43:02 2009 -0700 Proper support for end-of-stream signalling of MediaSources. commit afb038f2dc594c742e554b2d42b86ffc7434e3fc Author: Andreas Huber <andih@google.com> Date: Tue Jun 30 16:48:03 2009 -0700 Support for the "sniffing" of datasources to determine their probable mime type (along with confidence). Slightly refactored the MP3Extractor to share common code with the sniffer. commit ed3644b8bd56807e1db4b32433a93a2467f1f3c5 Author: Andreas Huber <andih@google.com> Date: Fri Jun 26 13:27:52 2009 -0700 I don't think prepare should automatically start the player. commit 11eb9b49dc4a0819d366682817cba68613f4fe12 Author: Andreas Huber <andih@google.com> Date: Fri Jun 26 11:09:22 2009 -0700 Somewhat better timing information implemented in the MP3Extractor. commit cab023a1751735c0ed26c8493498a91d8644c6c9 Author: Andreas Huber <andih@google.com> Date: Thu Jun 25 15:41:56 2009 -0700 Initial checkin for support of mp3 content, extraction and playback. Media extractors now abstracted in MediaExtractor baseclass. commit 40f4146bb3c647e56690dd9b0fa3ffdea60030dc Author: Andreas Huber <andih@google.com> Date: Wed Jun 24 10:41:12 2009 -0700 Make sure we only allocate surface buffers if there is video to be played. commit 7f85e7d92fe81740e102e09905b19354f4ac178e Author: Andreas Huber <andih@google.com> Date: Wed Jun 24 10:19:32 2009 -0700 Clean up a failed merge and implement stub for new API in MediaPlayerBase. commit f0e9d494a4c03402ea7b54891848ca5a50c421b2 Author: Andreas Huber <andih@google.com> Date: Fri Jun 12 15:38:03 2009 -0700 Support yuv420->rgb565 conversion if hardware color conversion is unavailable. commit 87223248b3099cd5261b94169f84cfd1eba56a8f Author: Andreas Huber <andih@google.com> Date: Fri Jun 12 09:35:56 2009 -0700 Remove unimplemented and currently unnecessary blocking option from MediaSource::ReadOptions. commit e07e651f0325892b797f3eb64dbeca0f317afdfd Author: Andreas Huber <andih@google.com> Date: Wed Jun 10 16:26:42 2009 -0700 Enable assertions in HTTPDataSource. commit 0dc97adebea1721156880f4b3d5c70f8d50c53b2 Author: Andreas Huber <andih@google.com> Date: Wed Jun 10 12:34:39 2009 -0700 delete foo. commit 7f05845dda496e7defee284d32509d127e785784 Author: Andreas Huber <andih@google.com> Date: Wed Jun 10 12:34:08 2009 -0700 added foo. commit e432970c51eeec4e5d549bdb3e2d3e48ffb4f0e7 Author: Andreas Huber <andih@google.com> Date: Wed Jun 10 11:44:31 2009 -0700 Make sure to initialize all fields in _all_ constructors... Use a Mutex instead of the raw pthread_mutex_t. commit d6286a6bbca639952491448fa3e609b775a8a814 Author: Andreas Huber <andih@google.com> Date: Tue Jun 9 17:11:49 2009 -0700 send_command is now implemented twice... ugly hack to get both shutdown working correctly and startup as well... playback now "works" on the emulator, too. commit 8d657e004677a7c5dae4479e8519bcaa94c18aad Author: Andreas Huber <andih@google.com> Date: Tue Jun 9 11:58:55 2009 -0700 Instead of erasing codec specific data once it's been output to the omx node, retain the data and just rewind after a stop. commit 279fede6ba35cbc37c35616c53406bf2c9720202 Author: Andreas Huber <andih@google.com> Date: Tue Jun 9 10:58:11 2009 -0700 Various fixes related to play/pause. Decoder now properly frees OMXMediaBuffers, made MemoryDealer destructor protected instead of public to prevent accidental stack-allocation of MemoryDealer objects. commit 0f717f15a6328efd3f34082929cd8ad2351095a9 Author: Andreas Huber <andih@google.com> Date: Tue Jun 9 08:44:12 2009 -0700 Make sure to rewind codec specific data on OMXDecoder::stop and reset AudioPlayer's notion of time mapping. commit 0cfb63e33270daf9b215214608ab383f93a31823 Author: Andreas Huber <andih@google.com> Date: Mon Jun 8 14:25:48 2009 -0700 Massive rename. commit 30ed8f476c4431a5275538958b3e281bd17d5eab Author: Andreas Huber <andih@google.com> Date: Mon Jun 8 13:52:41 2009 -0700 send_command and fill/empty buffer commands must be executed in sequence, send_command is now dispatched over the socket instead of through the binder interface to ensure this. commit b832d04011b2dd8a9632eb7e575bfe1addd1270d Author: Andreas Huber <andih@google.com> Date: Mon Jun 8 10:21:17 2009 -0700 MediaBuffers can now be "clone()", they sit on top of the same data but have their own range and metadata. Once a clone's refcount drops to 0 it decrements the refcount on the original buffer. commit 4e944672808bdf1ed40a69f3f602f93ebf7b4049 Author: Andreas Huber <andih@google.com> Date: Mon Jun 8 10:01:01 2009 -0700 Split MediaBuffer.h into MediaBufferGroup.h and MediaBuffer.h, same with their respective source files. commit ddb7529b4de4828db51432bd405fddd76cfa3b6a Author: Andreas Huber <andih@google.com> Date: Fri Jun 5 16:30:25 2009 -0700 More fudging with A/V sync... commit 1e5a4d8df08237b58c06c6527a33152ae931addf Author: Andreas Huber <andih@google.com> Date: Fri Jun 5 11:54:20 2009 -0700 API cleanup of MediaSource. Got rid of MediaRing as it really doesn't serve a purpose if OMX decoders are used. commit 0953c65232aa9753d17d2e25c5f5526eb810cf13 Author: Andreas Huber <andih@google.com> Date: Thu Jun 4 16:59:19 2009 -0700 Experimental TimedEventQueue and an attempt at a cleaner player implementation using it, OMXDecoder is now a little less verbose. commit 2085b8ea9484a21fa11f94194a27405d413aeecc Author: Andreas Huber <andih@google.com> Date: Thu Jun 4 10:17:01 2009 -0700 Properly bail out of the AV sync loop if the MediaPlayerImpl is stopped. commit 471f143458a8a0d93cb1bf9568c63c3967279dc6 Author: Andreas Huber <andih@google.com> Date: Wed Jun 3 17:32:27 2009 -0700 Now this is much better AV sync, defer seeking the audio source until we know where the video source synced to. commit 4010d371dbe62ce4971e13437776cba32cb0bb3d Author: Andreas Huber <andih@google.com> Date: Wed Jun 3 16:01:07 2009 -0700 Tweaked some of the heuristics in AV sync, OMXDecoder is now a little less verbose, seek to a sync sample at or _after_ the requested position. commit f3d4af1d5a4ac31fa02271b000f6c7ec593f0da9 Author: Andreas Huber <andih@google.com> Date: Wed Jun 3 15:33:58 2009 -0700 Change the TimeSource interface's APIs, prefer microseconds over milliseconds now, changed the way MediaPlayerImpl does AV sync. commit 08bb46fa61d12e852a424698ddb0c90700328f6e Author: Andreas Huber <andih@google.com> Date: Wed Jun 3 11:57:24 2009 -0700 Using framework's Mutex and Condition classes instead of pthread functions, much saner locking in OMXDecoder. commit 4d77b4e09e59bbf8c1af009ae7dc62b10af52fd6 Author: Andreas Huber <andih@google.com> Date: Tue Jun 2 17:12:03 2009 -0700 MediaSources now publish their format. commit 7e429a26c57bf936e1a08035d2d11839f9f00310 Author: Andreas Huber <andih@google.com> Date: Tue Jun 2 16:38:29 2009 -0700 MetaData is now implemented in terms of KeyedVector, MetaData is now reference counted. commit 0fa957aa5c9bcb6ec122ed7b5b97430039be6797 Author: Andreas Huber <andih@google.com> Date: Mon Jun 1 14:39:55 2009 -0700 Sync sample seek support. commit 78a5f090d02e7e6e1a146b29bdb7771eecaa2f86 Author: Andreas Huber <andih@google.com> Date: Mon Jun 1 13:51:11 2009 -0700 Distinguish shutdown from port-flush since one requires buffers to be freed and the other one doesn't. Fixes a problem with seek. commit e57781d3b2c985a74cd8b60e7c8f2d576459d038 Author: Andreas Huber <andih@google.com> Date: Mon Jun 1 10:52:20 2009 -0700 First shot at support for seeking a MediaSource. Sync frames are not properly handled yet. commit 0551cd78fda78bf66877fb26c5a55e5ae23d8367 Author: Andreas Huber <andih@google.com> Date: Thu May 28 16:23:38 2009 -0700 Preliminary support for camera input. commit f8ebafa65d10d5a06944de6b2e2992a33121b857 Author: Andreas Huber <andih@google.com> Date: Thu May 28 12:13:47 2009 -0700 Allow NativeMediaPlayer access to Surface's getISurface() method. commit b1a9fb36eeccd9f49d28c1c64b31fccbd6506eeb Author: Andreas Huber <andih@google.com> Date: Thu May 28 11:38:25 2009 -0700 Header files are now in their own genesis2 hierarchy below frameworks/base/include. commit f49b8b234b8f6ce83148787eef540dd920f04aa6 Author: Andreas Huber <andih@google.com> Date: Thu May 28 11:22:03 2009 -0700 moved MPEG4Writer into libgenesis2. commit 5224254919dac4728677335ea60429fa16f518b4 Author: Andreas Huber <andih@google.com> Date: Thu May 28 11:13:38 2009 -0700 Everything now lives in the android namespace, integrated errors with android errors, renamed MediaPlayer to MediaPlayerImpl. commit f9a9d84f7e0465ef5d1938a068655ba39527cf99 Author: Andreas Huber <andih@google.com> Date: Wed May 27 16:43:11 2009 -0700 Support for getPosition() and getDuration(). commit 3206cc5140bcbc88be039f8d94b954af63e69752 Author: Andreas Huber <andih@google.com> Date: Wed May 27 15:37:45 2009 -0700 Support for {MediaPlayer, GenesisPlayer, Genesis2Player}::isPlaying() commit 9f41b248e0dfce71bbc50f883750cec52ebfd1fe Author: Andreas Huber <andih@google.com> Date: Wed May 27 13:49:50 2009 -0700 Proper disconnection from the OMX service. commit cdcf47a2c31599109ee42277c7fa14af3c600091 Author: Andreas Huber <andih@google.com> Date: Tue May 26 17:27:06 2009 -0700 New player type for the genesis player in libmediaplayerservice. Playing video through an ISurface now. commit 0586af20ac9ae0403dd190bafd290b0f90549723 Author: Andreas Huber <andih@google.com> Date: Fri May 22 15:53:19 2009 -0700 Fix a buffer overflow error by adding padding in case we need to de-frame AVC data. commit eac62fff6ad2ffb7b75c0d4a1b9620d33e93fb9b Author: Andreas Huber <andih@google.com> Date: Fri May 22 13:35:30 2009 -0700 Support for http URIs in MediaPlayer, compensate for AudioTrack latency. commit a7db456e3173d94d6b5e457547e2a6a5f7a8e1cc Author: Andreas Huber <andih@google.com> Date: Fri May 22 11:33:25 2009 -0700 Support for URIs in MediaPlayer class, support for shoutcast streams in MediaPlayer, force shutdown of OMX nodes if they don't shutdown cleanly within a second. commit 1f0268f4f14ad5215668f34e06a420850174d665 Author: Andreas Huber <andih@google.com> Date: Fri May 22 08:33:16 2009 -0700 and foo.txt is gone again. commit 0feca4e0919f345704d78ec78894bac5027bd5ed Author: Andreas Huber <andih@dhcp-172-19-27-148.mtv.corp.google.com> Date: Fri May 22 08:28:49 2009 -0700 added foo.txt commit 9c7d04b35cdc895a2de3602dc46522f608bd205f Author: Andreas Huber <andih@google.com> Date: Fri May 22 08:28:04 2009 -0700 delete foo again commit fec3ca1720992cce51b9a92b86b945da88a6b16c Author: Andreas Huber <andih@google.com> Date: Fri May 22 08:27:45 2009 -0700 weird commit 30a6571302333146e537e537a3a8986245cca87c Author: Andreas Huber <andih@dhcp-172-19-27-148.mtv.corp.google.com> Date: Fri May 22 08:18:55 2009 -0700 reverse test commit dea3d99dd0fe9386e8a3c51954d9d0fe2be09d38 Author: Andreas Huber <andih@google.com> Date: Fri May 22 08:18:22 2009 -0700 test commit a9eeafec6e46072cac41e54ebdbd153cb7ae7518 Author: Andreas Huber <andih@google.com> Date: Thu May 21 16:37:50 2009 -0700 Use the video ring again for now. commit e05ff3d3c1c4f89ef499a2b172138a775fd694e3 Author: Andreas Huber <andih@google.com> Date: Thu May 21 15:42:19 2009 -0700 Export to .mp4 now works for MPEG4 video content. Fixed a bug in the sample table wrt co64 chunks. commit 5c44c540374a4e50c4e8aaab2b7a34aed6e1391e Author: Andreas Huber <andih@google.com> Date: Thu May 21 09:56:11 2009 -0700 Fix the build, all binder-related headers have moved and there's a new library libbinde r to link against now. commit 23e0a24404d16c898c2c6b3c7495ad303844bc71 Author: Andreas Huber <andih@google.com> Date: Thu May 21 08:42:04 2009 -0700 Initial checkin of MPEG4Writer, a class to export media sources into an mpeg4 file. MPEG4/AVC metadata (efss & friends) not currently working. commit 9cd77adb1ac194197364ffad00a0d7215f458e1a Author: Andreas Huber <andih@google.com> Date: Wed May 20 12:31:45 2009 -0700 New shell command to test recording/encoding. Various changes to OMXDecoder to support encoding properly. Now each OMXDecoder needs a dedicated thread on the client side since onEvent responses could block which would cause deadlocks if a node depends on the output of another node for example. commit 5c510fb55ffed7ec25305999ed9b6c1eec48d503 Author: Andreas Huber <andih@google.com> Date: Mon May 18 15:40:06 2009 -0700 Make media decoding the default in the genesis2 commandline app instead of shoutcast playback. commit f6c7a2040b8eb43326621f12c3a936e09b3a70e3 Author: Andreas Huber <andih@google.com> Date: Mon May 18 11:03:04 2009 -0700 The king is dead, long live the kind... Replaced old OMX binder interface with new OMX binder interface/socket hybrid. commit 0d4955eb5df9bfafb576d22f2ed572644d6d69ac Author: Andreas Huber <andih@google.com> Date: Thu May 14 12:39:34 2009 -0700 dynamic negotiation of NAL framing with the MPEG4Source, removed stale code from genesis2 commandline tool. Enable support form PV H264 software decoder. commit 380369f54a8027cbc9349305184d6bf527a24efe Author: Andreas Huber <andih@google.com> Date: Wed May 13 17:32:00 2009 -0700 Explore the option of running everything inside the mediaserver process by encapsulating the player in an IGenesisPlayer binder interface. commit b5cada56b9089a23e0c461352a97dc71e08b424d Author: Andreas Huber <andih@google.com> Date: Wed May 13 12:11:06 2009 -0700 Introduction of TimeSource class, clean shutdown of OMX Nodes. commit 89f6e582e605dcb2149eb67f73fe9345d4b9be2e Author: Andreas Huber <andih@google.com> Date: Tue May 12 17:11:56 2009 -0700 Helper classes MediaRing and MediaPlayer. commit 9aaed8739b4bb941473da367fb88f63224d2f1b7 Author: Andreas Huber <andih@google.com> Date: Tue May 12 14:19:18 2009 -0700 Using mmap to map a file instead of going through FILE* improves performance, who knew? commit ad097a78bbd002c59193ffdc39b64935928426c7 Author: Andreas Huber <andih@google.com> Date: Mon May 11 16:42:42 2009 -0700 Added a performance test, refactored AudioDecoder a little. commit bdd1b80ab3f3ba43e04f1f996e7fa84ed2e2728a Author: Andreas Huber <andih@google.com> Date: Fri May 8 16:19:29 2009 -0700 No more copying the output buffers coming out of the decoder, NAL separation also done in the extractor to avoid copying the data. commit 858955b42c29b8033841a8caaa56c2a449810ef3 Author: Andreas Huber <andih@google.com> Date: Fri May 8 14:02:38 2009 -0700 Change from a model in which the caller specifies the destination buffer to one where the MediaSource returns a buffer. commit 95098b6d4f8bdc37dfb6a109ca58db4d9e414e2d Author: Andreas Huber <andih@google.com> Date: Fri May 8 13:21:45 2009 -0700 Changes to support multiple pieces of codec specific data to the decoder, de-frame nal units from the h.264 stream and insert start codes commit 3941fcf87a48dda81683df727dfb1381f62d9184 Author: Andreas Huber <andih@google.com> Date: Thu May 7 16:37:45 2009 -0700 some refactoring. Most streams play now. commit d3152862840d4dd39068e5197bec8674f3d55dd5 Author: Andreas Huber <andih@google.com> Date: Thu May 7 15:37:32 2009 -0700 Misc. changes to support H264 decoding using the qcom hardware decoder commit d4ecdfe7af36e7468270d1c2cbbdc540efe0351e Author: Andreas Huber <andih@google.com> Date: Thu May 7 09:28:18 2009 -0700 Timestamps are now stored as a fraction, i.e. units and scale, also, PV appears to violate OMX specs by assuming timestamps are in milliseconds instead of microseconds. commit fe6a50943f885e065d4061aa727a54417e9f36db Author: Andreas Huber <andih@google.com> Date: Wed May 6 14:18:27 2009 -0700 Refactored commandline app instantiation of the decoder. commit da1ff2724d0e78e8b360d9a2c34c55dd0e2c0492 Author: Andreas Huber <andih@google.com> Date: Wed May 6 10:41:14 2009 -0700 Proper audio/video synchronization. commit 569ee3a23f600f4b6b87c6178c0223b06e97f201 Author: Andreas Huber <andih@google.com> Date: Wed May 6 09:17:20 2009 -0700 Preliminary support for timestamps. commit e4e302a89207550d8a7170d8c0afbd9c15047c84 Author: Andreas Huber <andih@google.com> Date: Tue May 5 15:13:12 2009 -0700 Another quick hack job to make CachingDataSource thread-safe as well. commit dfb376a58bd7c09aa04e89558186fcfe13fe696c Author: Andreas Huber <andih@google.com> Date: Tue May 5 15:08:14 2009 -0700 Quick rushjob to make FileSource and SampleTable thread safe. commit 02b29e7ebdab1f8b959b6c177ceb3b89d998f375 Author: Andreas Huber <andih@google.com> Date: Mon May 4 17:22:04 2009 -0700 Made video decoding work for mpeg4/h263 and mostly(?) with AVC content. commit 3a57b8ff15e48f5a0af03bf0a99c32e240dbb7ef Author: Andreas Huber <andih@google.com> Date: Mon May 4 09:26:42 2009 -0700 Getting rid of old sample code to drive the OMXNode binder interface. commit 5b046aed0cf524a57ea711bc1e43ea80e57b2cb3 Author: Andreas Huber <andih@google.com> Date: Fri May 1 16:21:11 2009 -0700 Moved files from the commandline utility into the framework, renamed MP3Decoder to AudioDecoder commit 7f3fbba21512a078b732cc52c7bd5ba5acb9a317 Author: Andreas Huber <andih@google.com> Date: Fri May 1 15:56:19 2009 -0700 Fixed buffer ordering and an off-by-1 error in the SampleTable code. Plays all AACs now :) commit 024d06baab8f2c540dde16f7e7a4b6dd57f638c7 Author: Andreas Huber <andih@google.com> Date: Fri May 1 11:52:48 2009 -0700 Some AAC content in .3gp or .mp4 files plays correctly now, other does not. Implemented ESFS parser. commit 1470427866b0d3e0ce6848b4edc01c13a09be289 Author: Andreas Huber <andih@google.com> Date: Thu Apr 30 15:19:31 2009 -0700 Another checkpoint, amr decoding now functional. commit e612d044439f56331b6a2bc4a88622e04d4d42e7 Author: Andreas Huber <andih@google.com> Date: Wed Apr 29 12:11:29 2009 -0700 string wrapper around String8, various fixes, shoutcast now works again. commit 2ea52dfb1a39bf3a223ffc87855e54919e2daff7 Author: Andreas Huber <andih@google.com> Date: Wed Apr 29 11:08:18 2009 -0700 Initial check-in of OMXNode binder interface. commit 7880d2957f74c0d7fba7679431ff5adb3780543b Author: Andreas Huber <andih@google.com> Date: Wed Apr 29 10:05:01 2009 -0700 Initial check-in of both libgenesis2 and the genesis2 commandline tool.
Diffstat (limited to 'media')
-rw-r--r--media/libmedia/Android.mk6
-rw-r--r--media/libmedia/IMediaPlayerService.cpp20
-rw-r--r--media/libmedia/IOMX.cpp561
-rw-r--r--media/libmediaplayerservice/Android.mk15
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.cpp88
-rw-r--r--media/libmediaplayerservice/MediaPlayerService.h20
-rw-r--r--media/libmediaplayerservice/StagefrightPlayer.cpp208
-rw-r--r--media/libmediaplayerservice/StagefrightPlayer.h60
-rw-r--r--media/libstagefright/Android.mk56
-rw-r--r--media/libstagefright/AudioPlayer.cpp285
-rw-r--r--media/libstagefright/CachingDataSource.cpp162
-rw-r--r--media/libstagefright/CameraSource.cpp226
-rw-r--r--media/libstagefright/DataSource.cpp76
-rw-r--r--media/libstagefright/ESDS.cpp196
-rw-r--r--media/libstagefright/FileSource.cpp50
-rw-r--r--media/libstagefright/HTTPDataSource.cpp174
-rw-r--r--media/libstagefright/HTTPStream.cpp285
-rw-r--r--media/libstagefright/MP3Extractor.cpp522
-rw-r--r--media/libstagefright/MPEG4Extractor.cpp937
-rw-r--r--media/libstagefright/MPEG4Writer.cpp641
-rw-r--r--media/libstagefright/MediaBuffer.cpp174
-rw-r--r--media/libstagefright/MediaBufferGroup.cpp88
-rw-r--r--media/libstagefright/MediaExtractor.cpp54
-rw-r--r--media/libstagefright/MediaPlayerImpl.cpp693
-rw-r--r--media/libstagefright/MediaSource.cpp60
-rw-r--r--media/libstagefright/MetaData.cpp232
-rw-r--r--media/libstagefright/MmapSource.cpp112
-rw-r--r--media/libstagefright/OMXClient.cpp369
-rw-r--r--media/libstagefright/OMXDecoder.cpp1329
-rw-r--r--media/libstagefright/QComHardwareRenderer.cpp139
-rw-r--r--media/libstagefright/SampleTable.cpp598
-rw-r--r--media/libstagefright/ShoutcastSource.cpp154
-rw-r--r--media/libstagefright/SoftwareRenderer.cpp173
-rw-r--r--media/libstagefright/SurfaceRenderer.cpp69
-rw-r--r--media/libstagefright/TimeSource.cpp40
-rw-r--r--media/libstagefright/TimedEventQueue.cpp206
-rw-r--r--media/libstagefright/Utils.cpp45
-rw-r--r--media/libstagefright/omx/Android.mk27
-rw-r--r--media/libstagefright/omx/OMX.cpp621
-rw-r--r--media/libstagefright/omx/OMX.h132
-rw-r--r--media/libstagefright/string.cpp83
41 files changed, 9974 insertions, 12 deletions
diff --git a/media/libmedia/Android.mk b/media/libmedia/Android.mk
index ebbf13f..cdaab04 100644
--- a/media/libmedia/Android.mk
+++ b/media/libmedia/Android.mk
@@ -18,7 +18,8 @@ LOCAL_SRC_FILES:= \
IMediaMetadataRetriever.cpp \
mediametadataretriever.cpp \
ToneGenerator.cpp \
- JetPlayer.cpp
+ JetPlayer.cpp \
+ IOMX.cpp
LOCAL_SHARED_LIBRARIES := \
libui libcutils libutils libbinder libsonivox
@@ -34,6 +35,7 @@ LOCAL_SHARED_LIBRARIES += libdl
endif
LOCAL_C_INCLUDES := \
- $(call include-path-for, graphics corecg)
+ $(call include-path-for, graphics corecg) \
+ $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include
include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libmedia/IMediaPlayerService.cpp b/media/libmedia/IMediaPlayerService.cpp
index 0f64259..8d2c360 100644
--- a/media/libmedia/IMediaPlayerService.cpp
+++ b/media/libmedia/IMediaPlayerService.cpp
@@ -17,12 +17,14 @@
#include <stdint.h>
#include <sys/types.h>
-#include <binder/Parcel.h>
+#include <binder/Parcel.h>
#include <binder/IMemory.h>
-#include <utils/Errors.h> // for status_t
#include <media/IMediaPlayerService.h>
#include <media/IMediaRecorder.h>
+#include <media/IOMX.h>
+
+#include <utils/Errors.h> // for status_t
namespace android {
@@ -33,6 +35,7 @@ enum {
DECODE_FD,
CREATE_MEDIA_RECORDER,
CREATE_METADATA_RETRIEVER,
+ CREATE_OMX,
};
class BpMediaPlayerService: public BpInterface<IMediaPlayerService>
@@ -110,6 +113,13 @@ public:
*pFormat = reply.readInt32();
return interface_cast<IMemory>(reply.readStrongBinder());
}
+
+ virtual sp<IOMX> createOMX() {
+ Parcel data, reply;
+ data.writeInterfaceToken(IMediaPlayerService::getInterfaceDescriptor());
+ remote()->transact(CREATE_OMX, data, &reply);
+ return interface_cast<IOMX>(reply.readStrongBinder());
+ }
};
IMPLEMENT_META_INTERFACE(MediaPlayerService, "android.media.IMediaPlayerService");
@@ -182,6 +192,12 @@ status_t BnMediaPlayerService::onTransact(
reply->writeStrongBinder(retriever->asBinder());
return NO_ERROR;
} break;
+ case CREATE_OMX: {
+ CHECK_INTERFACE(IMediaPlayerService, data, reply);
+ sp<IOMX> omx = createOMX();
+ reply->writeStrongBinder(omx->asBinder());
+ return NO_ERROR;
+ } break;
default:
return BBinder::onTransact(code, data, reply, flags);
}
diff --git a/media/libmedia/IOMX.cpp b/media/libmedia/IOMX.cpp
new file mode 100644
index 0000000..f2a657a
--- /dev/null
+++ b/media/libmedia/IOMX.cpp
@@ -0,0 +1,561 @@
+//#define LOG_NDEBUG 0
+#define LOG_TAG "IOMX"
+#include <utils/Log.h>
+
+#include <binder/IMemory.h>
+#include <binder/Parcel.h>
+#include <media/IOMX.h>
+
+namespace android {
+
+enum {
+ CONNECT = IBinder::FIRST_CALL_TRANSACTION,
+ LIST_NODES,
+ ALLOCATE_NODE,
+ FREE_NODE,
+ SEND_COMMAND,
+ GET_PARAMETER,
+ SET_PARAMETER,
+ USE_BUFFER,
+ ALLOC_BUFFER,
+ ALLOC_BUFFER_WITH_BACKUP,
+ FREE_BUFFER,
+ OBSERVE_NODE,
+ FILL_BUFFER,
+ EMPTY_BUFFER,
+ OBSERVER_ON_MSG,
+};
+
+static void *readVoidStar(const Parcel *parcel) {
+ // FIX if sizeof(void *) != sizeof(int32)
+ return (void *)parcel->readInt32();
+}
+
+static void writeVoidStar(void *x, Parcel *parcel) {
+ // FIX if sizeof(void *) != sizeof(int32)
+ parcel->writeInt32((int32_t)x);
+}
+
+class BpOMX : public BpInterface<IOMX> {
+public:
+ BpOMX(const sp<IBinder> &impl)
+ : BpInterface<IOMX>(impl) {
+ }
+
+#if IOMX_USES_SOCKETS
+ virtual status_t connect(int *sd) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ remote()->transact(CONNECT, data, &reply);
+
+ status_t err = reply.readInt32();
+ if (err == OK) {
+ *sd = dup(reply.readFileDescriptor());
+ } else {
+ *sd = -1;
+ }
+
+ return reply.readInt32();
+ }
+#endif
+
+ virtual status_t list_nodes(List<String8> *list) {
+ list->clear();
+
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ remote()->transact(LIST_NODES, data, &reply);
+
+ int32_t n = reply.readInt32();
+ for (int32_t i = 0; i < n; ++i) {
+ String8 s = reply.readString8();
+
+ list->push_back(s);
+ }
+
+ return OK;
+ }
+
+ virtual status_t allocate_node(const char *name, node_id *node) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ data.writeCString(name);
+ remote()->transact(ALLOCATE_NODE, data, &reply);
+
+ status_t err = reply.readInt32();
+ if (err == OK) {
+ *node = readVoidStar(&reply);
+ } else {
+ *node = 0;
+ }
+
+ return err;
+ }
+
+ virtual status_t free_node(node_id node) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ writeVoidStar(node, &data);
+ remote()->transact(FREE_NODE, data, &reply);
+
+ return reply.readInt32();
+ }
+
+ virtual status_t send_command(
+ node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ writeVoidStar(node, &data);
+ data.writeInt32(cmd);
+ data.writeInt32(param);
+ remote()->transact(SEND_COMMAND, data, &reply);
+
+ return reply.readInt32();
+ }
+
+ virtual status_t get_parameter(
+ node_id node, OMX_INDEXTYPE index,
+ void *params, size_t size) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ writeVoidStar(node, &data);
+ data.writeInt32(index);
+ data.writeInt32(size);
+ data.write(params, size);
+ remote()->transact(GET_PARAMETER, data, &reply);
+
+ status_t err = reply.readInt32();
+ if (err != OK) {
+ return err;
+ }
+
+ reply.read(params, size);
+
+ return OK;
+ }
+
+ virtual status_t set_parameter(
+ node_id node, OMX_INDEXTYPE index,
+ const void *params, size_t size) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ writeVoidStar(node, &data);
+ data.writeInt32(index);
+ data.writeInt32(size);
+ data.write(params, size);
+ remote()->transact(SET_PARAMETER, data, &reply);
+
+ return reply.readInt32();
+ }
+
+ virtual status_t use_buffer(
+ node_id node, OMX_U32 port_index, const sp<IMemory> &params,
+ buffer_id *buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ writeVoidStar(node, &data);
+ data.writeInt32(port_index);
+ data.writeStrongBinder(params->asBinder());
+ remote()->transact(USE_BUFFER, data, &reply);
+
+ status_t err = reply.readInt32();
+ if (err != OK) {
+ *buffer = 0;
+
+ return err;
+ }
+
+ *buffer = readVoidStar(&reply);
+
+ return err;
+ }
+
+ virtual status_t allocate_buffer(
+ node_id node, OMX_U32 port_index, size_t size,
+ buffer_id *buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ writeVoidStar(node, &data);
+ data.writeInt32(port_index);
+ data.writeInt32(size);
+ remote()->transact(ALLOC_BUFFER, data, &reply);
+
+ status_t err = reply.readInt32();
+ if (err != OK) {
+ *buffer = 0;
+
+ return err;
+ }
+
+ *buffer = readVoidStar(&reply);
+
+ return err;
+ }
+
+ virtual status_t allocate_buffer_with_backup(
+ node_id node, OMX_U32 port_index, const sp<IMemory> &params,
+ buffer_id *buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ writeVoidStar(node, &data);
+ data.writeInt32(port_index);
+ data.writeStrongBinder(params->asBinder());
+ remote()->transact(ALLOC_BUFFER_WITH_BACKUP, data, &reply);
+
+ status_t err = reply.readInt32();
+ if (err != OK) {
+ *buffer = 0;
+
+ return err;
+ }
+
+ *buffer = readVoidStar(&reply);
+
+ return err;
+ }
+
+ virtual status_t free_buffer(
+ node_id node, OMX_U32 port_index, buffer_id buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ writeVoidStar(node, &data);
+ data.writeInt32(port_index);
+ writeVoidStar(buffer, &data);
+ remote()->transact(FREE_BUFFER, data, &reply);
+
+ return reply.readInt32();
+ }
+
+#if !IOMX_USES_SOCKETS
+ virtual status_t observe_node(
+ node_id node, const sp<IOMXObserver> &observer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ writeVoidStar(node, &data);
+ data.writeStrongBinder(observer->asBinder());
+ remote()->transact(OBSERVE_NODE, data, &reply);
+
+ return reply.readInt32();
+ }
+
+ virtual void fill_buffer(node_id node, buffer_id buffer) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ writeVoidStar(node, &data);
+ writeVoidStar(buffer, &data);
+ remote()->transact(FILL_BUFFER, data, &reply, IBinder::FLAG_ONEWAY);
+ }
+
+ virtual void empty_buffer(
+ node_id node,
+ buffer_id buffer,
+ OMX_U32 range_offset, OMX_U32 range_length,
+ OMX_U32 flags, OMX_TICKS timestamp) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMX::getInterfaceDescriptor());
+ writeVoidStar(node, &data);
+ writeVoidStar(buffer, &data);
+ data.writeInt32(range_offset);
+ data.writeInt32(range_length);
+ data.writeInt32(flags);
+ data.writeInt64(timestamp);
+ remote()->transact(EMPTY_BUFFER, data, &reply, IBinder::FLAG_ONEWAY);
+ }
+#endif
+};
+
+IMPLEMENT_META_INTERFACE(OMX, "android.hardware.IOMX");
+
+////////////////////////////////////////////////////////////////////////////////
+
+#define CHECK_INTERFACE(interface, data, reply) \
+ do { if (!data.enforceInterface(interface::getInterfaceDescriptor())) { \
+ LOGW("Call incorrectly routed to " #interface); \
+ return PERMISSION_DENIED; \
+ } } while (0)
+
+status_t BnOMX::onTransact(
+ uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
+ switch (code) {
+#if IOMX_USES_SOCKETS
+ case CONNECT:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ int s;
+ status_t err = connect(&s);
+
+ reply->writeInt32(err);
+ if (err == OK) {
+ assert(s >= 0);
+ reply->writeDupFileDescriptor(s);
+ close(s);
+ s = -1;
+ } else {
+ assert(s == -1);
+ }
+
+ return NO_ERROR;
+ }
+#endif
+
+ case LIST_NODES:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ List<String8> list;
+ list_nodes(&list);
+
+ reply->writeInt32(list.size());
+ for (List<String8>::iterator it = list.begin();
+ it != list.end(); ++it) {
+ reply->writeString8(*it);
+ }
+
+ return NO_ERROR;
+ }
+
+ case ALLOCATE_NODE:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node;
+ status_t err = allocate_node(data.readCString(), &node);
+ reply->writeInt32(err);
+ if (err == OK) {
+ writeVoidStar(node, reply);
+ }
+
+ return NO_ERROR;
+ }
+
+ case FREE_NODE:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node = readVoidStar(&data);
+
+ reply->writeInt32(free_node(node));
+
+ return NO_ERROR;
+ }
+
+ case SEND_COMMAND:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node = readVoidStar(&data);
+
+ OMX_COMMANDTYPE cmd =
+ static_cast<OMX_COMMANDTYPE>(data.readInt32());
+
+ OMX_S32 param = data.readInt32();
+ reply->writeInt32(send_command(node, cmd, param));
+
+ return NO_ERROR;
+ }
+
+ case GET_PARAMETER:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node = readVoidStar(&data);
+ OMX_INDEXTYPE index = static_cast<OMX_INDEXTYPE>(data.readInt32());
+
+ size_t size = data.readInt32();
+
+ // XXX I am not happy with this but Parcel::readInplace didn't work.
+ void *params = malloc(size);
+ data.read(params, size);
+
+ status_t err = get_parameter(node, index, params, size);
+
+ reply->writeInt32(err);
+
+ if (err == OK) {
+ reply->write(params, size);
+ }
+
+ free(params);
+ params = NULL;
+
+ return NO_ERROR;
+ }
+
+ case SET_PARAMETER:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node = readVoidStar(&data);
+ OMX_INDEXTYPE index = static_cast<OMX_INDEXTYPE>(data.readInt32());
+
+ size_t size = data.readInt32();
+ void *params = const_cast<void *>(data.readInplace(size));
+
+ reply->writeInt32(set_parameter(node, index, params, size));
+
+ return NO_ERROR;
+ }
+
+ case USE_BUFFER:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node = readVoidStar(&data);
+ OMX_U32 port_index = data.readInt32();
+ sp<IMemory> params =
+ interface_cast<IMemory>(data.readStrongBinder());
+
+ buffer_id buffer;
+ status_t err = use_buffer(node, port_index, params, &buffer);
+ reply->writeInt32(err);
+
+ if (err == OK) {
+ writeVoidStar(buffer, reply);
+ }
+
+ return NO_ERROR;
+ }
+
+ case ALLOC_BUFFER:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node = readVoidStar(&data);
+ OMX_U32 port_index = data.readInt32();
+ size_t size = data.readInt32();
+
+ buffer_id buffer;
+ status_t err = allocate_buffer(node, port_index, size, &buffer);
+ reply->writeInt32(err);
+
+ if (err == OK) {
+ writeVoidStar(buffer, reply);
+ }
+
+ return NO_ERROR;
+ }
+
+ case ALLOC_BUFFER_WITH_BACKUP:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node = readVoidStar(&data);
+ OMX_U32 port_index = data.readInt32();
+ sp<IMemory> params =
+ interface_cast<IMemory>(data.readStrongBinder());
+
+ buffer_id buffer;
+ status_t err = allocate_buffer_with_backup(
+ node, port_index, params, &buffer);
+
+ reply->writeInt32(err);
+
+ if (err == OK) {
+ writeVoidStar(buffer, reply);
+ }
+
+ return NO_ERROR;
+ }
+
+ case FREE_BUFFER:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node = readVoidStar(&data);
+ OMX_U32 port_index = data.readInt32();
+ buffer_id buffer = readVoidStar(&data);
+ reply->writeInt32(free_buffer(node, port_index, buffer));
+
+ return NO_ERROR;
+ }
+
+#if !IOMX_USES_SOCKETS
+ case OBSERVE_NODE:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node = readVoidStar(&data);
+ sp<IOMXObserver> observer =
+ interface_cast<IOMXObserver>(data.readStrongBinder());
+ reply->writeInt32(observe_node(node, observer));
+
+ return NO_ERROR;
+ }
+
+ case FILL_BUFFER:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node = readVoidStar(&data);
+ buffer_id buffer = readVoidStar(&data);
+ fill_buffer(node, buffer);
+
+ return NO_ERROR;
+ }
+
+ case EMPTY_BUFFER:
+ {
+ CHECK_INTERFACE(IOMX, data, reply);
+
+ node_id node = readVoidStar(&data);
+ buffer_id buffer = readVoidStar(&data);
+ OMX_U32 range_offset = data.readInt32();
+ OMX_U32 range_length = data.readInt32();
+ OMX_U32 flags = data.readInt32();
+ OMX_TICKS timestamp = data.readInt64();
+
+ empty_buffer(
+ node, buffer, range_offset, range_length,
+ flags, timestamp);
+
+ return NO_ERROR;
+ }
+#endif
+
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+class BpOMXObserver : public BpInterface<IOMXObserver> {
+public:
+ BpOMXObserver(const sp<IBinder> &impl)
+ : BpInterface<IOMXObserver>(impl) {
+ }
+
+ virtual void on_message(const omx_message &msg) {
+ Parcel data, reply;
+ data.writeInterfaceToken(IOMXObserver::getInterfaceDescriptor());
+ data.write(&msg, sizeof(msg));
+
+ remote()->transact(OBSERVER_ON_MSG, data, &reply, IBinder::FLAG_ONEWAY);
+ }
+};
+
+IMPLEMENT_META_INTERFACE(OMXObserver, "android.hardware.IOMXObserver");
+
+status_t BnOMXObserver::onTransact(
+ uint32_t code, const Parcel &data, Parcel *reply, uint32_t flags) {
+ switch (code) {
+ case OBSERVER_ON_MSG:
+ {
+ CHECK_INTERFACE(IOMXObserver, data, reply);
+
+ omx_message msg;
+ data.read(&msg, sizeof(msg));
+
+ // XXX Could use readInplace maybe?
+ on_message(msg);
+
+ return NO_ERROR;
+ }
+
+ default:
+ return BBinder::onTransact(code, data, reply, flags);
+ }
+}
+
+} // namespace android
diff --git a/media/libmediaplayerservice/Android.mk b/media/libmediaplayerservice/Android.mk
index 0877142..6978b3d 100644
--- a/media/libmediaplayerservice/Android.mk
+++ b/media/libmediaplayerservice/Android.mk
@@ -29,9 +29,22 @@ LOCAL_SHARED_LIBRARIES := \
libandroid_runtime
LOCAL_C_INCLUDES := external/tremor/Tremor \
- $(call include-path-for, graphics corecg)
+ $(call include-path-for, graphics corecg) \
+ $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include
LOCAL_MODULE:= libmediaplayerservice
+ifeq ($(BUILD_WITH_STAGEFRIGHT),true)
+ LOCAL_SRC_FILES += StagefrightPlayer.cpp
+
+ LOCAL_SHARED_LIBRARIES += \
+ libstagefright \
+ libstagefright_omx
+
+ LOCAL_C_INCLUDES += $(TOP)/frameworks/base/media/libstagefright/omx
+
+ LOCAL_CFLAGS += -DBUILD_WITH_STAGEFRIGHT -DUSE_STAGEFRIGHT
+endif
+
include $(BUILD_SHARED_LIBRARY)
diff --git a/media/libmediaplayerservice/MediaPlayerService.cpp b/media/libmediaplayerservice/MediaPlayerService.cpp
index 493dc13..c575f6c 100644
--- a/media/libmediaplayerservice/MediaPlayerService.cpp
+++ b/media/libmediaplayerservice/MediaPlayerService.cpp
@@ -58,6 +58,15 @@
#include "MidiFile.h"
#include "VorbisPlayer.h"
#include <media/PVPlayer.h>
+#if USE_STAGEFRIGHT
+#include "StagefrightPlayer.h"
+#endif
+
+#ifdef BUILD_WITH_STAGEFRIGHT
+#include <OMX.h>
+#else
+#include <media/IOMX.h>
+#endif
/* desktop Linux needs a little help with gettid() */
#if defined(HAVE_GETTID) && !defined(HAVE_ANDROID_OS)
@@ -186,6 +195,10 @@ typedef struct {
const player_type playertype;
} extmap;
extmap FILE_EXTS [] = {
+#if USE_STAGEFRIGHT
+ {".mp4", STAGEFRIGHT_PLAYER},
+ {".3gp", STAGEFRIGHT_PLAYER},
+#endif
{".mid", SONIVOX_PLAYER},
{".midi", SONIVOX_PLAYER},
{".smf", SONIVOX_PLAYER},
@@ -271,6 +284,14 @@ sp<IMediaPlayer> MediaPlayerService::create(pid_t pid, const sp<IMediaPlayerClie
return c;
}
+sp<IOMX> MediaPlayerService::createOMX() {
+#ifdef BUILD_WITH_STAGEFRIGHT
+ return new OMX;
+#else
+ return NULL;
+#endif
+}
+
status_t MediaPlayerService::AudioCache::dump(int fd, const Vector<String16>& args) const
{
const size_t SIZE = 256;
@@ -577,6 +598,7 @@ void MediaPlayerService::Client::disconnect()
p = mPlayer;
}
mClient.clear();
+
mPlayer.clear();
// clear the notification to prevent callbacks to dead client
@@ -624,13 +646,16 @@ static player_type getPlayerType(int fd, int64_t offset, int64_t length)
EAS_Shutdown(easdata);
}
+#if USE_STAGEFRIGHT
+ return STAGEFRIGHT_PLAYER;
+#endif
+
// Fall through to PV
return PV_PLAYER;
}
static player_type getPlayerType(const char* url)
{
-
// use MidiFile for MIDI extensions
int lenURL = strlen(url);
for (int i = 0; i < NELEM(FILE_EXTS); ++i) {
@@ -643,6 +668,10 @@ static player_type getPlayerType(const char* url)
}
}
+#if USE_STAGEFRIGHT
+ return STAGEFRIGHT_PLAYER;
+#endif
+
// Fall through to PV
return PV_PLAYER;
}
@@ -666,6 +695,17 @@ static sp<MediaPlayerBase> createPlayer(player_type playerType, void* cookie,
LOGV(" create VorbisPlayer");
p = new VorbisPlayer();
break;
+#if USE_STAGEFRIGHT
+ case STAGEFRIGHT_PLAYER:
+ LOGV(" create StagefrightPlayer");
+ p = new StagefrightPlayer;
+ break;
+#else
+ case STAGEFRIGHT_PLAYER:
+ LOG_ALWAYS_FATAL(
+ "Should not be here, stagefright player not enabled.");
+ break;
+#endif
}
if (p != NULL) {
if (p->initCheck() == NO_ERROR) {
@@ -1136,7 +1176,8 @@ Exit:
#undef LOG_TAG
#define LOG_TAG "AudioSink"
MediaPlayerService::AudioOutput::AudioOutput()
-{
+ : mCallback(NULL),
+ mCallbackCookie(NULL) {
mTrack = 0;
mStreamType = AudioSystem::MUSIC;
mLeftVolume = 1.0;
@@ -1206,8 +1247,13 @@ float MediaPlayerService::AudioOutput::msecsPerFrame() const
return mMsecsPerFrame;
}
-status_t MediaPlayerService::AudioOutput::open(uint32_t sampleRate, int channelCount, int format, int bufferCount)
+status_t MediaPlayerService::AudioOutput::open(
+ uint32_t sampleRate, int channelCount, int format, int bufferCount,
+ AudioCallback cb, void *cookie)
{
+ mCallback = cb;
+ mCallbackCookie = cookie;
+
// Check argument "bufferCount" against the mininum buffer count
if (bufferCount < mMinBufferCount) {
LOGD("bufferCount (%d) is too small and increased to %d", bufferCount, mMinBufferCount);
@@ -1228,7 +1274,17 @@ status_t MediaPlayerService::AudioOutput::open(uint32_t sampleRate, int channelC
}
frameCount = (sampleRate*afFrameCount*bufferCount)/afSampleRate;
- AudioTrack *t = new AudioTrack(mStreamType, sampleRate, format, channelCount, frameCount);
+
+ AudioTrack *t;
+ if (mCallback != NULL) {
+ t = new AudioTrack(
+ mStreamType, sampleRate, format, channelCount, frameCount,
+ 0 /* flags */, CallbackWrapper, this);
+ } else {
+ t = new AudioTrack(
+ mStreamType, sampleRate, format, channelCount, frameCount);
+ }
+
if ((t == 0) || (t->initCheck() != NO_ERROR)) {
LOGE("Unable to create audio track");
delete t;
@@ -1254,6 +1310,8 @@ void MediaPlayerService::AudioOutput::start()
ssize_t MediaPlayerService::AudioOutput::write(const void* buffer, size_t size)
{
+ LOG_FATAL_IF(mCallback != NULL, "Don't call write if supplying a callback.");
+
//LOGV("write(%p, %u)", buffer, size);
if (mTrack) return mTrack->write(buffer, size);
return NO_INIT;
@@ -1294,6 +1352,20 @@ void MediaPlayerService::AudioOutput::setVolume(float left, float right)
}
}
+// static
+void MediaPlayerService::AudioOutput::CallbackWrapper(
+ int event, void *cookie, void *info) {
+ if (event != AudioTrack::EVENT_MORE_DATA) {
+ return;
+ }
+
+ AudioOutput *me = (AudioOutput *)cookie;
+ AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info;
+
+ (*me->mCallback)(
+ me, buffer->raw, buffer->size, me->mCallbackCookie);
+}
+
#undef LOG_TAG
#define LOG_TAG "AudioCache"
MediaPlayerService::AudioCache::AudioCache(const char* name) :
@@ -1314,8 +1386,14 @@ float MediaPlayerService::AudioCache::msecsPerFrame() const
return mMsecsPerFrame;
}
-status_t MediaPlayerService::AudioCache::open(uint32_t sampleRate, int channelCount, int format, int bufferCount)
+status_t MediaPlayerService::AudioCache::open(
+ uint32_t sampleRate, int channelCount, int format, int bufferCount,
+ AudioCallback cb, void *cookie)
{
+ if (cb != NULL) {
+ return UNKNOWN_ERROR; // TODO: implement this.
+ }
+
LOGV("open(%u, %d, %d, %d)", sampleRate, channelCount, format, bufferCount);
if (mHeap->getHeapID() < 0) return NO_INIT;
mSampleRate = sampleRate;
diff --git a/media/libmediaplayerservice/MediaPlayerService.h b/media/libmediaplayerservice/MediaPlayerService.h
index db3d5d7..94cb917 100644
--- a/media/libmediaplayerservice/MediaPlayerService.h
+++ b/media/libmediaplayerservice/MediaPlayerService.h
@@ -35,6 +35,7 @@ typedef int32_t MetadataType;
class IMediaRecorder;
class IMediaMetadataRetriever;
+class IOMX;
#define CALLBACK_ANTAGONIZER 0
#if CALLBACK_ANTAGONIZER
@@ -75,7 +76,12 @@ class MediaPlayerService : public BnMediaPlayerService
virtual ssize_t frameSize() const;
virtual uint32_t latency() const;
virtual float msecsPerFrame() const;
- virtual status_t open(uint32_t sampleRate, int channelCount, int format, int bufferCount=4);
+
+ virtual status_t open(
+ uint32_t sampleRate, int channelCount,
+ int format, int bufferCount,
+ AudioCallback cb, void *cookie);
+
virtual void start();
virtual ssize_t write(const void* buffer, size_t size);
virtual void stop();
@@ -90,8 +96,12 @@ class MediaPlayerService : public BnMediaPlayerService
static int getMinBufferCount();
private:
static void setMinBufferCount();
+ static void CallbackWrapper(
+ int event, void *me, void *info);
AudioTrack* mTrack;
+ AudioCallback mCallback;
+ void * mCallbackCookie;
int mStreamType;
float mLeftVolume;
float mRightVolume;
@@ -119,7 +129,12 @@ class MediaPlayerService : public BnMediaPlayerService
virtual ssize_t frameSize() const { return ssize_t(mChannelCount * ((mFormat == AudioSystem::PCM_16_BIT)?sizeof(int16_t):sizeof(u_int8_t))); }
virtual uint32_t latency() const;
virtual float msecsPerFrame() const;
- virtual status_t open(uint32_t sampleRate, int channelCount, int format, int bufferCount=1);
+
+ virtual status_t open(
+ uint32_t sampleRate, int channelCount, int format,
+ int bufferCount = 1,
+ AudioCallback cb = NULL, void *cookie = NULL);
+
virtual void start() {}
virtual ssize_t write(const void* buffer, size_t size);
virtual void stop() {}
@@ -166,6 +181,7 @@ public:
virtual sp<IMediaPlayer> create(pid_t pid, const sp<IMediaPlayerClient>& client, int fd, int64_t offset, int64_t length);
virtual sp<IMemory> decode(const char* url, uint32_t *pSampleRate, int* pNumChannels, int* pFormat);
virtual sp<IMemory> decode(int fd, int64_t offset, int64_t length, uint32_t *pSampleRate, int* pNumChannels, int* pFormat);
+ virtual sp<IOMX> createOMX();
virtual status_t dump(int fd, const Vector<String16>& args);
diff --git a/media/libmediaplayerservice/StagefrightPlayer.cpp b/media/libmediaplayerservice/StagefrightPlayer.cpp
new file mode 100644
index 0000000..ad1afbb
--- /dev/null
+++ b/media/libmediaplayerservice/StagefrightPlayer.cpp
@@ -0,0 +1,208 @@
+//#define LOG_NDEBUG 0
+#define LOG_TAG "StagefrightPlayer"
+#include <utils/Log.h>
+
+#include "StagefrightPlayer.h"
+#include <media/stagefright/MediaPlayerImpl.h>
+
+namespace android {
+
+StagefrightPlayer::StagefrightPlayer()
+ : mPlayer(NULL) {
+ LOGV("StagefrightPlayer");
+}
+
+StagefrightPlayer::~StagefrightPlayer() {
+ LOGV("~StagefrightPlayer");
+ reset();
+ LOGV("~StagefrightPlayer done.");
+}
+
+status_t StagefrightPlayer::initCheck() {
+ LOGV("initCheck");
+ return OK;
+}
+
+status_t StagefrightPlayer::setDataSource(const char *url) {
+ LOGV("setDataSource('%s')", url);
+
+ reset();
+ mPlayer = new MediaPlayerImpl(url);
+
+ status_t err = mPlayer->initCheck();
+ if (err != OK) {
+ delete mPlayer;
+ mPlayer = NULL;
+ } else {
+ mPlayer->setAudioSink(mAudioSink);
+ }
+
+ return err;
+}
+
+status_t StagefrightPlayer::setDataSource(int fd, int64_t offset, int64_t length) {
+ LOGV("setDataSource(%d, %lld, %lld)", fd, offset, length);
+
+ reset();
+ mPlayer = new MediaPlayerImpl(fd, offset, length);
+
+ status_t err = mPlayer->initCheck();
+ if (err != OK) {
+ delete mPlayer;
+ mPlayer = NULL;
+ } else {
+ mPlayer->setAudioSink(mAudioSink);
+ }
+
+ return err;
+}
+
+status_t StagefrightPlayer::setVideoSurface(const sp<ISurface> &surface) {
+ LOGV("setVideoSurface");
+
+ if (mPlayer == NULL) {
+ return NO_INIT;
+ }
+
+ mPlayer->setISurface(surface);
+
+ return OK;
+}
+
+status_t StagefrightPlayer::prepare() {
+ LOGV("prepare");
+
+ if (mPlayer == NULL) {
+ return NO_INIT;
+ }
+
+ sendEvent(
+ MEDIA_SET_VIDEO_SIZE,
+ mPlayer->getWidth(), mPlayer->getHeight());
+
+ return OK;
+}
+
+status_t StagefrightPlayer::prepareAsync() {
+ LOGV("prepareAsync");
+
+ status_t err = prepare();
+
+ if (err != OK) {
+ return err;
+ }
+
+ sendEvent(MEDIA_PREPARED);
+
+ return OK;
+}
+
+status_t StagefrightPlayer::start() {
+ LOGV("start");
+
+ if (mPlayer == NULL) {
+ return NO_INIT;
+ }
+
+ mPlayer->play();
+
+ return OK;
+}
+
+status_t StagefrightPlayer::stop() {
+ LOGV("stop");
+
+ if (mPlayer == NULL) {
+ return NO_INIT;
+ }
+
+ reset();
+
+ return OK;
+}
+
+status_t StagefrightPlayer::pause() {
+ LOGV("pause");
+
+ if (mPlayer == NULL) {
+ return NO_INIT;
+ }
+
+ mPlayer->pause();
+
+ return OK;
+}
+
+bool StagefrightPlayer::isPlaying() {
+ LOGV("isPlaying");
+ return mPlayer != NULL && mPlayer->isPlaying();
+}
+
+status_t StagefrightPlayer::seekTo(int msec) {
+ LOGV("seekTo");
+
+ if (mPlayer == NULL) {
+ return NO_INIT;
+ }
+
+ status_t err = mPlayer->seekTo((int64_t)msec * 1000);
+
+ sendEvent(MEDIA_SEEK_COMPLETE);
+
+ return err;
+}
+
+status_t StagefrightPlayer::getCurrentPosition(int *msec) {
+ LOGV("getCurrentPosition");
+
+ if (mPlayer == NULL) {
+ return NO_INIT;
+ }
+
+ *msec = mPlayer->getPosition() / 1000;
+ return OK;
+}
+
+status_t StagefrightPlayer::getDuration(int *msec) {
+ LOGV("getDuration");
+
+ if (mPlayer == NULL) {
+ return NO_INIT;
+ }
+
+ *msec = mPlayer->getDuration() / 1000;
+ return OK;
+}
+
+status_t StagefrightPlayer::reset() {
+ LOGV("reset");
+
+ delete mPlayer;
+ mPlayer = NULL;
+
+ return OK;
+}
+
+status_t StagefrightPlayer::setLooping(int loop) {
+ LOGV("setLooping");
+ return UNKNOWN_ERROR;
+}
+
+player_type StagefrightPlayer::playerType() {
+ LOGV("playerType");
+ return STAGEFRIGHT_PLAYER;
+}
+
+status_t StagefrightPlayer::invoke(const Parcel &request, Parcel *reply) {
+ return INVALID_OPERATION;
+}
+
+void StagefrightPlayer::setAudioSink(const sp<AudioSink> &audioSink) {
+ MediaPlayerInterface::setAudioSink(audioSink);
+
+ if (mPlayer != NULL) {
+ mPlayer->setAudioSink(audioSink);
+ }
+}
+
+} // namespace android
diff --git a/media/libmediaplayerservice/StagefrightPlayer.h b/media/libmediaplayerservice/StagefrightPlayer.h
new file mode 100644
index 0000000..f214872
--- /dev/null
+++ b/media/libmediaplayerservice/StagefrightPlayer.h
@@ -0,0 +1,60 @@
+/*
+**
+** Copyright 2009, 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 ANDROID_STAGEFRIGHTPLAYER_H
+#define ANDROID_STAGEFRIGHTPLAYER_H
+
+#include <media/MediaPlayerInterface.h>
+
+namespace android {
+
+class MediaPlayerImpl;
+
+class StagefrightPlayer : public MediaPlayerInterface {
+public:
+ StagefrightPlayer();
+ virtual ~StagefrightPlayer();
+
+ virtual status_t initCheck();
+ virtual status_t setDataSource(const char *url);
+ virtual status_t setDataSource(int fd, int64_t offset, int64_t length);
+ virtual status_t setVideoSurface(const sp<ISurface> &surface);
+ virtual status_t prepare();
+ virtual status_t prepareAsync();
+ virtual status_t start();
+ virtual status_t stop();
+ virtual status_t pause();
+ virtual bool isPlaying();
+ virtual status_t seekTo(int msec);
+ virtual status_t getCurrentPosition(int *msec);
+ virtual status_t getDuration(int *msec);
+ virtual status_t reset();
+ virtual status_t setLooping(int loop);
+ virtual player_type playerType();
+ virtual status_t invoke(const Parcel &request, Parcel *reply);
+ virtual void setAudioSink(const sp<AudioSink> &audioSink);
+
+private:
+ MediaPlayerImpl *mPlayer;
+
+ StagefrightPlayer(const StagefrightPlayer &);
+ StagefrightPlayer &operator=(const StagefrightPlayer &);
+};
+
+} // namespace android
+
+#endif // ANDROID_STAGEFRIGHTPLAYER_H
diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk
new file mode 100644
index 0000000..6e7936c
--- /dev/null
+++ b/media/libstagefright/Android.mk
@@ -0,0 +1,56 @@
+ifeq ($(BUILD_WITH_STAGEFRIGHT),true)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+LOCAL_SRC_FILES:= \
+ CachingDataSource.cpp \
+ DataSource.cpp \
+ FileSource.cpp \
+ HTTPDataSource.cpp \
+ HTTPStream.cpp \
+ MP3Extractor.cpp \
+ MPEG4Extractor.cpp \
+ MPEG4Writer.cpp \
+ MediaBuffer.cpp \
+ MediaBufferGroup.cpp \
+ MediaExtractor.cpp \
+ MediaPlayerImpl.cpp \
+ MediaSource.cpp \
+ MetaData.cpp \
+ MmapSource.cpp \
+ QComHardwareRenderer.cpp \
+ SampleTable.cpp \
+ ShoutcastSource.cpp \
+ SoftwareRenderer.cpp \
+ SurfaceRenderer.cpp \
+ TimeSource.cpp \
+ TimedEventQueue.cpp \
+ Utils.cpp \
+ AudioPlayer.cpp \
+ ESDS.cpp \
+ OMXClient.cpp \
+ OMXDecoder.cpp \
+ string.cpp
+
+LOCAL_C_INCLUDES:= \
+ $(TOP)/external/opencore/extern_libs_v2/khronos/openmax/include \
+ $(TOP)/external/opencore/android
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libmedia \
+ libutils \
+ libcutils \
+ libui
+
+LOCAL_CFLAGS += -Wno-multichar
+
+LOCAL_PRELINK_MODULE:= false
+
+LOCAL_MODULE:= libstagefright
+
+include $(BUILD_SHARED_LIBRARY)
+
+include $(call all-makefiles-under,$(LOCAL_PATH))
+endif
diff --git a/media/libstagefright/AudioPlayer.cpp b/media/libstagefright/AudioPlayer.cpp
new file mode 100644
index 0000000..17c72b9
--- /dev/null
+++ b/media/libstagefright/AudioPlayer.cpp
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#undef NDEBUG
+#include <assert.h>
+
+#define LOG_TAG "AudioPlayer"
+#include <utils/Log.h>
+
+#include <media/AudioTrack.h>
+#include <media/stagefright/AudioPlayer.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+AudioPlayer::AudioPlayer(const sp<MediaPlayerBase::AudioSink> &audioSink)
+ : mSource(NULL),
+ mAudioTrack(NULL),
+ mInputBuffer(NULL),
+ mSampleRate(0),
+ mLatencyUs(0),
+ mFrameSize(0),
+ mNumFramesPlayed(0),
+ mPositionTimeMediaUs(-1),
+ mPositionTimeRealUs(-1),
+ mSeeking(false),
+ mStarted(false),
+ mAudioSink(audioSink) {
+}
+
+AudioPlayer::~AudioPlayer() {
+ if (mStarted) {
+ stop();
+ }
+}
+
+void AudioPlayer::setSource(MediaSource *source) {
+ assert(mSource == NULL);
+ mSource = source;
+}
+
+void AudioPlayer::start() {
+ assert(!mStarted);
+ assert(mSource != NULL);
+
+ status_t err = mSource->start();
+ assert(err == OK);
+
+ sp<MetaData> format = mSource->getFormat();
+ const char *mime;
+ bool success = format->findCString(kKeyMIMEType, &mime);
+ assert(success);
+ assert(!strcasecmp(mime, "audio/raw"));
+
+ success = format->findInt32(kKeySampleRate, &mSampleRate);
+ assert(success);
+
+ int32_t numChannels;
+ success = format->findInt32(kKeyChannelCount, &numChannels);
+ assert(success);
+
+ if (mAudioSink.get() != NULL) {
+ status_t err = mAudioSink->open(
+ mSampleRate, numChannels, AudioSystem::PCM_16_BIT,
+ DEFAULT_AUDIOSINK_BUFFERCOUNT,
+ &AudioPlayer::AudioSinkCallback, this);
+ assert(err == OK);
+
+ mLatencyUs = (int64_t)mAudioSink->latency() * 1000;
+ mFrameSize = mAudioSink->frameSize();
+
+ mAudioSink->start();
+ } else {
+ mAudioTrack = new AudioTrack(
+ AudioSystem::MUSIC, mSampleRate, AudioSystem::PCM_16_BIT,
+ numChannels, 8192, 0, &AudioCallback, this, 0);
+
+ assert(mAudioTrack->initCheck() == OK);
+
+ mLatencyUs = (int64_t)mAudioTrack->latency() * 1000;
+ mFrameSize = mAudioTrack->frameSize();
+
+ mAudioTrack->start();
+ }
+
+ mStarted = true;
+}
+
+void AudioPlayer::pause() {
+ assert(mStarted);
+
+ if (mAudioSink.get() != NULL) {
+ mAudioSink->pause();
+ } else {
+ mAudioTrack->stop();
+ }
+}
+
+void AudioPlayer::resume() {
+ assert(mStarted);
+
+ if (mAudioSink.get() != NULL) {
+ mAudioSink->start();
+ } else {
+ mAudioTrack->start();
+ }
+}
+
+void AudioPlayer::stop() {
+ assert(mStarted);
+
+ if (mAudioSink.get() != NULL) {
+ mAudioSink->stop();
+ } else {
+ mAudioTrack->stop();
+
+ delete mAudioTrack;
+ mAudioTrack = NULL;
+ }
+
+ // Make sure to release any buffer we hold onto so that the
+ // source is able to stop().
+ if (mInputBuffer != NULL) {
+ LOGI("AudioPlayer releasing input buffer.");
+
+ mInputBuffer->release();
+ mInputBuffer = NULL;
+ }
+
+ mSource->stop();
+
+ mNumFramesPlayed = 0;
+ mPositionTimeMediaUs = -1;
+ mPositionTimeRealUs = -1;
+ mSeeking = false;
+ mStarted = false;
+}
+
+// static
+void AudioPlayer::AudioCallback(int event, void *user, void *info) {
+ static_cast<AudioPlayer *>(user)->AudioCallback(event, info);
+}
+
+// static
+void AudioPlayer::AudioSinkCallback(
+ MediaPlayerBase::AudioSink *audioSink,
+ void *buffer, size_t size, void *cookie) {
+ AudioPlayer *me = (AudioPlayer *)cookie;
+
+ me->fillBuffer(buffer, size);
+}
+
+void AudioPlayer::AudioCallback(int event, void *info) {
+ if (event != AudioTrack::EVENT_MORE_DATA) {
+ return;
+ }
+
+ AudioTrack::Buffer *buffer = (AudioTrack::Buffer *)info;
+ fillBuffer(buffer->raw, buffer->size);
+}
+
+void AudioPlayer::fillBuffer(void *data, size_t size) {
+ if (mNumFramesPlayed == 0) {
+ LOGI("AudioCallback");
+ }
+
+ size_t size_done = 0;
+ size_t size_remaining = size;
+ while (size_remaining > 0) {
+ MediaSource::ReadOptions options;
+
+ {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mSeeking) {
+ options.setSeekTo(mSeekTimeUs);
+
+ if (mInputBuffer != NULL) {
+ mInputBuffer->release();
+ mInputBuffer = NULL;
+ }
+ mSeeking = false;
+ }
+ }
+
+ if (mInputBuffer == NULL) {
+ status_t err = mSource->read(&mInputBuffer, &options);
+
+ assert((err == OK && mInputBuffer != NULL)
+ || (err != OK && mInputBuffer == NULL));
+
+ if (err != OK) {
+ memset((char *)data + size_done, 0, size_remaining);
+ break;
+ }
+
+ int32_t units, scale;
+ bool success =
+ mInputBuffer->meta_data()->findInt32(kKeyTimeUnits, &units);
+ success = success &&
+ mInputBuffer->meta_data()->findInt32(kKeyTimeScale, &scale);
+ assert(success);
+
+ Mutex::Autolock autoLock(mLock);
+ mPositionTimeMediaUs = (int64_t)units * 1000000 / scale;
+ mPositionTimeRealUs =
+ ((mNumFramesPlayed + size_done / 4) * 1000000) / mSampleRate; // XXX
+ }
+
+ if (mInputBuffer->range_length() == 0) {
+ mInputBuffer->release();
+ mInputBuffer = NULL;
+
+ continue;
+ }
+
+ size_t copy = size_remaining;
+ if (copy > mInputBuffer->range_length()) {
+ copy = mInputBuffer->range_length();
+ }
+
+ memcpy((char *)data + size_done,
+ (const char *)mInputBuffer->data() + mInputBuffer->range_offset(),
+ copy);
+
+ mInputBuffer->set_range(mInputBuffer->range_offset() + copy,
+ mInputBuffer->range_length() - copy);
+
+ size_done += copy;
+ size_remaining -= copy;
+ }
+
+ Mutex::Autolock autoLock(mLock);
+ mNumFramesPlayed += size / mFrameSize;
+}
+
+int64_t AudioPlayer::getRealTimeUs() {
+ Mutex::Autolock autoLock(mLock);
+ return getRealTimeUsLocked();
+}
+
+int64_t AudioPlayer::getRealTimeUsLocked() const {
+ return -mLatencyUs + (mNumFramesPlayed * 1000000) / mSampleRate;
+}
+
+int64_t AudioPlayer::getMediaTimeUs() {
+ Mutex::Autolock autoLock(mLock);
+
+ return mPositionTimeMediaUs + (getRealTimeUsLocked() - mPositionTimeRealUs);
+}
+
+bool AudioPlayer::getMediaTimeMapping(
+ int64_t *realtime_us, int64_t *mediatime_us) {
+ Mutex::Autolock autoLock(mLock);
+
+ *realtime_us = mPositionTimeRealUs;
+ *mediatime_us = mPositionTimeMediaUs;
+
+ return mPositionTimeRealUs != -1 || mPositionTimeMediaUs != -1;
+}
+
+status_t AudioPlayer::seekTo(int64_t time_us) {
+ Mutex::Autolock autoLock(mLock);
+
+ mSeeking = true;
+ mSeekTimeUs = time_us;
+
+ return OK;
+}
+
+}
diff --git a/media/libstagefright/CachingDataSource.cpp b/media/libstagefright/CachingDataSource.cpp
new file mode 100644
index 0000000..0fd71d5
--- /dev/null
+++ b/media/libstagefright/CachingDataSource.cpp
@@ -0,0 +1,162 @@
+/*
+ * Copyright (C) 2009 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 <media/stagefright/CachingDataSource.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <stdlib.h>
+#include <string.h>
+
+namespace android {
+
+CachingDataSource::CachingDataSource(
+ DataSource *source, size_t pageSize, int numPages)
+ : mSource(source),
+ mData(malloc(pageSize * numPages)),
+ mPageSize(pageSize),
+ mFirst(NULL),
+ mLast(NULL) {
+ for (int i = 0; i < numPages; ++i) {
+ Page *page = new Page;
+ page->mPrev = mLast;
+ page->mNext = NULL;
+
+ if (mLast == NULL) {
+ mFirst = page;
+ } else {
+ mLast->mNext = page;
+ }
+
+ mLast = page;
+
+ page->mOffset = -1;
+ page->mLength = 0;
+ page->mData = (char *)mData + mPageSize * i;
+ }
+}
+
+CachingDataSource::~CachingDataSource() {
+ Page *page = mFirst;
+ while (page != NULL) {
+ Page *next = page->mNext;
+ delete page;
+ page = next;
+ }
+ mFirst = mLast = NULL;
+
+ free(mData);
+ mData = NULL;
+
+ delete mSource;
+ mSource = NULL;
+}
+
+status_t CachingDataSource::InitCheck() const {
+ return OK;
+}
+
+ssize_t CachingDataSource::read_at(off_t offset, void *data, size_t size) {
+ Mutex::Autolock autoLock(mLock);
+
+ size_t total = 0;
+ while (size > 0) {
+ Page *page = mFirst;
+ while (page != NULL) {
+ if (page->mOffset >= 0 && offset >= page->mOffset
+ && offset < page->mOffset + page->mLength) {
+ break;
+ }
+ page = page->mNext;
+ }
+
+ if (page == NULL) {
+ page = allocate_page();
+ page->mOffset = offset - offset % mPageSize;
+ ssize_t n = mSource->read_at(page->mOffset, page->mData, mPageSize);
+ if (n < 0) {
+ page->mLength = 0;
+ } else {
+ page->mLength = (size_t)n;
+ }
+ mFirst->mPrev = page;
+ page->mNext = mFirst;
+ page->mPrev = NULL;
+ mFirst = page;
+
+ if (n < 0) {
+ return n;
+ }
+
+ if (offset >= page->mOffset + page->mLength) {
+ break;
+ }
+ } else {
+ // Move "page" to the front in LRU order.
+ if (page->mNext != NULL) {
+ page->mNext->mPrev = page->mPrev;
+ } else {
+ mLast = page->mPrev;
+ }
+
+ if (page->mPrev != NULL) {
+ page->mPrev->mNext = page->mNext;
+ } else {
+ mFirst = page->mNext;
+ }
+
+ mFirst->mPrev = page;
+ page->mNext = mFirst;
+ page->mPrev = NULL;
+ mFirst = page;
+ }
+
+ size_t copy = page->mLength - (offset - page->mOffset);
+ if (copy > size) {
+ copy = size;
+ }
+ memcpy(data,(const char *)page->mData + (offset - page->mOffset),
+ copy);
+
+ total += copy;
+
+ if (page->mLength < mPageSize) {
+ // This was the final page. There is no more data beyond it.
+ break;
+ }
+
+ offset += copy;
+ size -= copy;
+ data = (char *)data + copy;
+ }
+
+ return total;
+}
+
+CachingDataSource::Page *CachingDataSource::allocate_page() {
+ // The last page is the least recently used, i.e. oldest.
+
+ Page *page = mLast;
+
+ page->mPrev->mNext = NULL;
+ mLast = page->mPrev;
+ page->mPrev = NULL;
+
+ return page;
+}
+
+} // namespace android
diff --git a/media/libstagefright/CameraSource.cpp b/media/libstagefright/CameraSource.cpp
new file mode 100644
index 0000000..ee12873
--- /dev/null
+++ b/media/libstagefright/CameraSource.cpp
@@ -0,0 +1,226 @@
+/*
+ * Copyright (C) 2009 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 <sys/time.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <OMX_Component.h>
+
+#include <binder/IServiceManager.h>
+#include <media/stagefright/CameraSource.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MetaData.h>
+#include <ui/ICameraClient.h>
+#include <ui/ICameraService.h>
+#include <ui/Overlay.h>
+#include <utils/String16.h>
+
+namespace android {
+
+class CameraBuffer : public MediaBuffer {
+public:
+ CameraBuffer(const sp<IMemory> &frame)
+ : MediaBuffer(frame->pointer(), frame->size()),
+ mFrame(frame) {
+ }
+
+ sp<IMemory> releaseFrame() {
+ sp<IMemory> frame = mFrame;
+ mFrame.clear();
+ return frame;
+ }
+
+private:
+ sp<IMemory> mFrame;
+};
+
+class CameraSourceClient : public BnCameraClient {
+public:
+ CameraSourceClient()
+ : mSource(NULL) {
+ }
+
+ virtual void notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) {
+ assert(mSource != NULL);
+ mSource->notifyCallback(msgType, ext1, ext2);
+ }
+
+ virtual void dataCallback(int32_t msgType, const sp<IMemory> &data) {
+ assert(mSource != NULL);
+ mSource->dataCallback(msgType, data);
+ }
+
+ void setCameraSource(CameraSource *source) {
+ mSource = source;
+ }
+
+private:
+ CameraSource *mSource;
+};
+
+class DummySurface : public BnSurface {
+public:
+ DummySurface() {}
+
+ virtual status_t registerBuffers(const BufferHeap &buffers) {
+ return OK;
+ }
+
+ virtual void postBuffer(ssize_t offset) {
+ }
+
+ virtual void unregisterBuffers() {
+ }
+
+ virtual sp<OverlayRef> createOverlay(
+ uint32_t w, uint32_t h, int32_t format) {
+ return NULL;
+ }
+};
+
+// static
+CameraSource *CameraSource::Create() {
+ sp<IServiceManager> sm = defaultServiceManager();
+
+ sp<ICameraService> service =
+ interface_cast<ICameraService>(
+ sm->getService(String16("media.camera")));
+
+ sp<CameraSourceClient> client = new CameraSourceClient;
+ sp<ICamera> camera = service->connect(client);
+
+ CameraSource *source = new CameraSource(camera, client);
+ client->setCameraSource(source);
+
+ return source;
+}
+
+CameraSource::CameraSource(
+ const sp<ICamera> &camera, const sp<ICameraClient> &client)
+ : mCamera(camera),
+ mCameraClient(client),
+ mNumFrames(0),
+ mStarted(false) {
+ printf("params: \"%s\"\n", mCamera->getParameters().string());
+}
+
+CameraSource::~CameraSource() {
+ if (mStarted) {
+ stop();
+ }
+
+ mCamera->disconnect();
+}
+
+status_t CameraSource::start(MetaData *) {
+ assert(!mStarted);
+
+ status_t err = mCamera->lock();
+ assert(err == OK);
+
+ err = mCamera->setPreviewDisplay(new DummySurface);
+ assert(err == OK);
+ mCamera->setPreviewCallbackFlag(1);
+ mCamera->startPreview();
+ assert(err == OK);
+
+ mStarted = true;
+
+ return OK;
+}
+
+status_t CameraSource::stop() {
+ assert(mStarted);
+
+ mCamera->stopPreview();
+ mCamera->unlock();
+
+ mStarted = false;
+
+ return OK;
+}
+
+sp<MetaData> CameraSource::getFormat() {
+ sp<MetaData> meta = new MetaData;
+ meta->setCString(kKeyMIMEType, "video/raw");
+ meta->setInt32(kKeyColorFormat, OMX_COLOR_FormatYUV420SemiPlanar);
+ meta->setInt32(kKeyWidth, 480);
+ meta->setInt32(kKeyHeight, 320);
+
+ return meta;
+}
+
+status_t CameraSource::read(
+ MediaBuffer **buffer, const ReadOptions *options) {
+ assert(mStarted);
+
+ *buffer = NULL;
+
+ int64_t seekTimeUs;
+ if (options && options->getSeekTo(&seekTimeUs)) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ sp<IMemory> frame;
+
+ {
+ Mutex::Autolock autoLock(mLock);
+ while (mFrames.empty()) {
+ mFrameAvailableCondition.wait(mLock);
+ }
+
+ frame = *mFrames.begin();
+ mFrames.erase(mFrames.begin());
+ }
+
+ int count = mNumFrames++;
+
+ *buffer = new CameraBuffer(frame);
+
+ (*buffer)->meta_data()->clear();
+ (*buffer)->meta_data()->setInt32(kKeyTimeScale, 15);
+ (*buffer)->meta_data()->setInt32(kKeyTimeUnits, count);
+
+ (*buffer)->add_ref();
+ (*buffer)->setObserver(this);
+
+ return OK;
+}
+
+void CameraSource::notifyCallback(int32_t msgType, int32_t ext1, int32_t ext2) {
+ printf("notifyCallback %d, %d, %d\n", msgType, ext1, ext2);
+}
+
+void CameraSource::dataCallback(int32_t msgType, const sp<IMemory> &data) {
+ Mutex::Autolock autoLock(mLock);
+
+ mFrames.push_back(data);
+ mFrameAvailableCondition.signal();
+}
+
+void CameraSource::signalBufferReturned(MediaBuffer *_buffer) {
+ CameraBuffer *buffer = static_cast<CameraBuffer *>(_buffer);
+
+ mCamera->releaseRecordingFrame(buffer->releaseFrame());
+
+ buffer->setObserver(NULL);
+ buffer->release();
+ buffer = NULL;
+}
+
+} // namespace android
diff --git a/media/libstagefright/DataSource.cpp b/media/libstagefright/DataSource.cpp
new file mode 100644
index 0000000..6e6b43d
--- /dev/null
+++ b/media/libstagefright/DataSource.cpp
@@ -0,0 +1,76 @@
+/*
+ * Copyright (C) 2009 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 <media/stagefright/DataSource.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MP3Extractor.h>
+#include <media/stagefright/MPEG4Extractor.h>
+#include <utils/String8.h>
+
+namespace android {
+
+status_t DataSource::getSize(off_t *size) {
+ *size = 0;
+
+ return ERROR_UNSUPPORTED;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+Mutex DataSource::gSnifferMutex;
+List<DataSource::SnifferFunc> DataSource::gSniffers;
+
+bool DataSource::sniff(String8 *mimeType, float *confidence) {
+ *mimeType = "";
+ *confidence = 0.0f;
+
+ Mutex::Autolock autoLock(gSnifferMutex);
+ for (List<SnifferFunc>::iterator it = gSniffers.begin();
+ it != gSniffers.end(); ++it) {
+ String8 newMimeType;
+ float newConfidence;
+ if ((*it)(this, &newMimeType, &newConfidence)) {
+ if (newConfidence > *confidence) {
+ *mimeType = newMimeType;
+ *confidence = newConfidence;
+ }
+ }
+ }
+
+ return *confidence > 0.0;
+}
+
+// static
+void DataSource::RegisterSniffer(SnifferFunc func) {
+ Mutex::Autolock autoLock(gSnifferMutex);
+
+ for (List<SnifferFunc>::iterator it = gSniffers.begin();
+ it != gSniffers.end(); ++it) {
+ if (*it == func) {
+ return;
+ }
+ }
+
+ gSniffers.push_back(func);
+}
+
+// static
+void DataSource::RegisterDefaultSniffers() {
+ RegisterSniffer(SniffMP3);
+ RegisterSniffer(SniffMPEG4);
+}
+
+} // namespace android
diff --git a/media/libstagefright/ESDS.cpp b/media/libstagefright/ESDS.cpp
new file mode 100644
index 0000000..53b92a0
--- /dev/null
+++ b/media/libstagefright/ESDS.cpp
@@ -0,0 +1,196 @@
+/*
+ * Copyright (C) 2009 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 <media/stagefright/ESDS.h>
+
+#include <string.h>
+
+namespace android {
+
+ESDS::ESDS(const void *data, size_t size)
+ : mData(new uint8_t[size]),
+ mSize(size),
+ mInitCheck(NO_INIT),
+ mDecoderSpecificOffset(0),
+ mDecoderSpecificLength(0) {
+ memcpy(mData, data, size);
+
+ mInitCheck = parse();
+}
+
+ESDS::~ESDS() {
+ delete[] mData;
+ mData = NULL;
+}
+
+status_t ESDS::InitCheck() const {
+ return mInitCheck;
+}
+
+status_t ESDS::getCodecSpecificInfo(const void **data, size_t *size) const {
+ if (mInitCheck != OK) {
+ return mInitCheck;
+ }
+
+ *data = &mData[mDecoderSpecificOffset];
+ *size = mDecoderSpecificLength;
+
+ return OK;
+}
+
+status_t ESDS::skipDescriptorHeader(
+ size_t offset, size_t size,
+ uint8_t *tag, size_t *data_offset, size_t *data_size) const {
+ if (size == 0) {
+ return ERROR_MALFORMED;
+ }
+
+ *tag = mData[offset++];
+ --size;
+
+ *data_size = 0;
+ bool more;
+ do {
+ if (size == 0) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t x = mData[offset++];
+ --size;
+
+ *data_size = (*data_size << 7) | (x & 0x7f);
+ more = (x & 0x80) != 0;
+ }
+ while (more);
+
+ if (*data_size > size) {
+ return ERROR_MALFORMED;
+ }
+
+ *data_offset = offset;
+
+ return OK;
+}
+
+status_t ESDS::parse() {
+ uint8_t tag;
+ size_t data_offset;
+ size_t data_size;
+ status_t err =
+ skipDescriptorHeader(0, mSize, &tag, &data_offset, &data_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (tag != kTag_ESDescriptor) {
+ return ERROR_MALFORMED;
+ }
+
+ return parseESDescriptor(data_offset, data_size);
+}
+
+status_t ESDS::parseESDescriptor(size_t offset, size_t size) {
+ if (size < 3) {
+ return ERROR_MALFORMED;
+ }
+
+ offset += 2; // skip ES_ID
+ size -= 2;
+
+ unsigned streamDependenceFlag = mData[offset] & 0x80;
+ unsigned URL_Flag = mData[offset] & 0x40;
+ unsigned OCRstreamFlag = mData[offset] & 0x20;
+
+ ++offset;
+ --size;
+
+ if (streamDependenceFlag) {
+ offset += 2;
+ size -= 2;
+ }
+
+ if (URL_Flag) {
+ if (offset >= size) {
+ return ERROR_MALFORMED;
+ }
+ unsigned URLlength = mData[offset];
+ offset += URLlength + 1;
+ size -= URLlength + 1;
+ }
+
+ if (OCRstreamFlag) {
+ offset += 2;
+ size -= 2;
+ }
+
+ if (offset >= size) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t tag;
+ size_t sub_offset, sub_size;
+ status_t err = skipDescriptorHeader(
+ offset, size, &tag, &sub_offset, &sub_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (tag != kTag_DecoderConfigDescriptor) {
+ return ERROR_MALFORMED;
+ }
+
+ err = parseDecoderConfigDescriptor(sub_offset, sub_size);
+
+ return err;
+}
+
+status_t ESDS::parseDecoderConfigDescriptor(size_t offset, size_t size) {
+ if (size < 13) {
+ return ERROR_MALFORMED;
+ }
+
+ offset += 13;
+ size -= 13;
+
+ if (size == 0) {
+ mDecoderSpecificOffset = 0;
+ mDecoderSpecificLength = 0;
+ return OK;
+ }
+
+ uint8_t tag;
+ size_t sub_offset, sub_size;
+ status_t err = skipDescriptorHeader(
+ offset, size, &tag, &sub_offset, &sub_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (tag != kTag_DecoderSpecificInfo) {
+ return ERROR_MALFORMED;
+ }
+
+ mDecoderSpecificOffset = sub_offset;
+ mDecoderSpecificLength = sub_size;
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/FileSource.cpp b/media/libstagefright/FileSource.cpp
new file mode 100644
index 0000000..c26d0a0
--- /dev/null
+++ b/media/libstagefright/FileSource.cpp
@@ -0,0 +1,50 @@
+/*
+ * Copyright (C) 2009 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 <media/stagefright/FileSource.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+namespace android {
+
+FileSource::FileSource(const char *filename)
+ : mFile(fopen(filename, "rb")) {
+}
+
+FileSource::~FileSource() {
+ if (mFile != NULL) {
+ fclose(mFile);
+ mFile = NULL;
+ }
+}
+
+status_t FileSource::InitCheck() const {
+ return mFile != NULL ? OK : NO_INIT;
+}
+
+ssize_t FileSource::read_at(off_t offset, void *data, size_t size) {
+ Mutex::Autolock autoLock(mLock);
+
+ int err = fseeko(mFile, offset, SEEK_SET);
+ assert(err != -1);
+
+ ssize_t result = fread(data, 1, size, mFile);
+
+ return result;
+}
+
+} // namespace android
diff --git a/media/libstagefright/HTTPDataSource.cpp b/media/libstagefright/HTTPDataSource.cpp
new file mode 100644
index 0000000..d1f8cd4
--- /dev/null
+++ b/media/libstagefright/HTTPDataSource.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <stdlib.h>
+
+#include <media/stagefright/HTTPDataSource.h>
+#include <media/stagefright/HTTPStream.h>
+#include <media/stagefright/string.h>
+
+namespace android {
+
+HTTPDataSource::HTTPDataSource(const char *uri)
+ : mHost(NULL),
+ mPort(0),
+ mPath(NULL),
+ mBuffer(malloc(kBufferSize)),
+ mBufferLength(0),
+ mBufferOffset(0) {
+ assert(!strncasecmp("http://", uri, 7));
+
+ string host;
+ string path;
+ int port;
+
+ char *slash = strchr(uri + 7, '/');
+ if (slash == NULL) {
+ host = uri + 7;
+ path = "/";
+ } else {
+ host = string(uri + 7, slash - (uri + 7));
+ path = slash;
+ }
+
+ char *colon = strchr(host.c_str(), ':');
+ if (colon == NULL) {
+ port = 80;
+ } else {
+ char *end;
+ long tmp = strtol(colon + 1, &end, 10);
+ assert(end > colon + 1);
+ assert(tmp > 0 && tmp < 65536);
+ port = tmp;
+
+ host = string(host, 0, colon - host.c_str());
+ }
+
+ LOGI("Connecting to host '%s', port %d, path '%s'",
+ host.c_str(), port, path.c_str());
+
+ mHost = strdup(host.c_str());
+ mPort = port;
+ mPath = strdup(path.c_str());
+
+ status_t err = mHttp.connect(mHost, mPort);
+ assert(err == OK);
+}
+
+HTTPDataSource::HTTPDataSource(const char *host, int port, const char *path)
+ : mHost(strdup(host)),
+ mPort(port),
+ mPath(strdup(path)),
+ mBuffer(malloc(kBufferSize)),
+ mBufferLength(0),
+ mBufferOffset(0) {
+ status_t err = mHttp.connect(mHost, mPort);
+ assert(err == OK);
+}
+
+HTTPDataSource::~HTTPDataSource() {
+ mHttp.disconnect();
+
+ free(mBuffer);
+ mBuffer = NULL;
+
+ free(mPath);
+ mPath = NULL;
+}
+
+ssize_t HTTPDataSource::read_at(off_t offset, void *data, size_t size) {
+ if (offset >= mBufferOffset && offset < mBufferOffset + mBufferLength) {
+ size_t num_bytes_available = mBufferLength - (offset - mBufferOffset);
+
+ size_t copy = num_bytes_available;
+ if (copy > size) {
+ copy = size;
+ }
+
+ memcpy(data, (const char *)mBuffer + (offset - mBufferOffset), copy);
+
+ return copy;
+ }
+
+ mBufferOffset = offset;
+ mBufferLength = 0;
+
+ char host[128];
+ sprintf(host, "Host: %s\r\n", mHost);
+
+ char range[128];
+ sprintf(range, "Range: bytes=%ld-%ld\r\n\r\n",
+ mBufferOffset, mBufferOffset + kBufferSize - 1);
+
+ int http_status;
+
+ status_t err;
+ int attempt = 1;
+ for (;;) {
+ if ((err = mHttp.send("GET ")) != OK
+ || (err = mHttp.send(mPath)) != OK
+ || (err = mHttp.send(" HTTP/1.1\r\n")) != OK
+ || (err = mHttp.send(host)) != OK
+ || (err = mHttp.send(range)) != OK
+ || (err = mHttp.send("\r\n")) != OK
+ || (err = mHttp.receive_header(&http_status)) != OK) {
+
+ if (attempt == 3) {
+ return err;
+ }
+
+ mHttp.connect(mHost, mPort);
+ ++attempt;
+ } else {
+ break;
+ }
+ }
+
+ if ((http_status / 100) != 2) {
+ return UNKNOWN_ERROR;
+ }
+
+ string value;
+ if (!mHttp.find_header_value("Content-Length", &value)) {
+ return UNKNOWN_ERROR;
+ }
+
+ char *end;
+ unsigned long contentLength = strtoul(value.c_str(), &end, 10);
+
+ ssize_t num_bytes_received = mHttp.receive(mBuffer, contentLength);
+
+ if (num_bytes_received <= 0) {
+ return num_bytes_received;
+ }
+
+ mBufferLength = (size_t)num_bytes_received;
+
+ size_t copy = mBufferLength;
+ if (copy > size) {
+ copy = size;
+ }
+
+ memcpy(data, mBuffer, copy);
+
+ return copy;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/HTTPStream.cpp b/media/libstagefright/HTTPStream.cpp
new file mode 100644
index 0000000..29e6f72
--- /dev/null
+++ b/media/libstagefright/HTTPStream.cpp
@@ -0,0 +1,285 @@
+/*
+ * Copyright (C) 2009 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 <sys/socket.h>
+
+#include <arpa/inet.h>
+#include <assert.h>
+#include <ctype.h>
+#include <errno.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <media/stagefright/HTTPStream.h>
+
+namespace android {
+
+// static
+const char *HTTPStream::kStatusKey = ":status:";
+
+HTTPStream::HTTPStream()
+ : mState(READY),
+ mSocket(-1) {
+}
+
+HTTPStream::~HTTPStream() {
+ disconnect();
+}
+
+status_t HTTPStream::connect(const char *server, int port) {
+ status_t err = OK;
+
+ if (mState == CONNECTED) {
+ return ERROR_ALREADY_CONNECTED;
+ }
+
+ assert(mSocket == -1);
+ mSocket = socket(AF_INET, SOCK_STREAM, 0);
+
+ if (mSocket < 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ struct hostent *ent = gethostbyname(server);
+ if (ent == NULL) {
+ err = ERROR_UNKNOWN_HOST;
+ goto exit1;
+ }
+
+ struct sockaddr_in addr;
+ addr.sin_family = AF_INET;
+ addr.sin_port = htons(port);
+ addr.sin_addr.s_addr = *(in_addr_t *)ent->h_addr;
+ memset(addr.sin_zero, 0, sizeof(addr.sin_zero));
+
+ if (::connect(mSocket, (const struct sockaddr *)&addr, sizeof(addr)) < 0) {
+ err = ERROR_CANNOT_CONNECT;
+ goto exit1;
+ }
+
+ mState = CONNECTED;
+
+ return OK;
+
+exit1:
+ close(mSocket);
+ mSocket = -1;
+
+ return err;
+}
+
+status_t HTTPStream::disconnect() {
+ if (mState != CONNECTED) {
+ return ERROR_NOT_CONNECTED;
+ }
+
+ assert(mSocket >= 0);
+ close(mSocket);
+ mSocket = -1;
+
+ mState = READY;
+
+ return OK;
+}
+
+status_t HTTPStream::send(const char *data, size_t size) {
+ if (mState != CONNECTED) {
+ return ERROR_NOT_CONNECTED;
+ }
+
+ while (size > 0) {
+ ssize_t n = ::send(mSocket, data, size, 0);
+
+ if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ disconnect();
+
+ return ERROR_IO;
+ } else if (n == 0) {
+ disconnect();
+
+ return ERROR_CONNECTION_LOST;
+ }
+
+ size -= (size_t)n;
+ data += (size_t)n;
+ }
+
+ return OK;
+}
+
+status_t HTTPStream::send(const char *data) {
+ return send(data, strlen(data));
+}
+
+status_t HTTPStream::receive_line(char *line, size_t size) {
+ if (mState != CONNECTED) {
+ return ERROR_NOT_CONNECTED;
+ }
+
+ bool saw_CR = false;
+ size_t length = 0;
+
+ for (;;) {
+ char c;
+ ssize_t n = recv(mSocket, &c, 1, 0);
+ if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ disconnect();
+
+ return ERROR_IO;
+ } else if (n == 0) {
+ disconnect();
+
+ return ERROR_CONNECTION_LOST;
+ }
+
+ if (saw_CR && c == '\n') {
+ // We have a complete line.
+
+ line[length - 1] = '\0';
+ return OK;
+ }
+
+ saw_CR = (c == '\r');
+
+ assert(length + 1 < size);
+ line[length++] = c;
+ }
+}
+
+status_t HTTPStream::receive_header(int *http_status) {
+ *http_status = -1;
+ mHeaders.clear();
+
+ char line[256];
+ status_t err = receive_line(line, sizeof(line));
+ if (err != OK) {
+ return err;
+ }
+
+ mHeaders.add(string(kStatusKey), string(line));
+
+ char *spacePos = strchr(line, ' ');
+ if (spacePos == NULL) {
+ // Malformed response?
+ return UNKNOWN_ERROR;
+ }
+
+ char *status_start = spacePos + 1;
+ char *status_end = status_start;
+ while (isdigit(*status_end)) {
+ ++status_end;
+ }
+
+ if (status_end == status_start) {
+ // Malformed response, status missing?
+ return UNKNOWN_ERROR;
+ }
+
+ memmove(line, status_start, status_end - status_start);
+ line[status_end - status_start] = '\0';
+
+ long tmp = strtol(line, NULL, 10);
+ if (tmp < 0 || tmp > 999) {
+ return UNKNOWN_ERROR;
+ }
+
+ *http_status = (int)tmp;
+
+ for (;;) {
+ err = receive_line(line, sizeof(line));
+ if (err != OK) {
+ return err;
+ }
+
+ if (*line == '\0') {
+ // Empty line signals the end of the header.
+ break;
+ }
+
+ // puts(line);
+
+ char *colonPos = strchr(line, ':');
+ if (colonPos == NULL) {
+ mHeaders.add(string(line), string());
+ } else {
+ char *end_of_key = colonPos;
+ while (end_of_key > line && isspace(end_of_key[-1])) {
+ --end_of_key;
+ }
+
+ char *start_of_value = colonPos + 1;
+ while (isspace(*start_of_value)) {
+ ++start_of_value;
+ }
+
+ *end_of_key = '\0';
+
+ mHeaders.add(string(line), string(start_of_value));
+ }
+ }
+
+ return OK;
+}
+
+ssize_t HTTPStream::receive(void *data, size_t size) {
+ size_t total = 0;
+ while (total < size) {
+ ssize_t n = recv(mSocket, (char *)data + total, size - total, 0);
+
+ if (n < 0) {
+ if (errno == EINTR) {
+ continue;
+ }
+
+ disconnect();
+ return ERROR_IO;
+ } else if (n == 0) {
+ disconnect();
+
+ return ERROR_CONNECTION_LOST;
+ }
+
+ total += (size_t)n;
+ }
+
+ return (ssize_t)total;
+}
+
+bool HTTPStream::find_header_value(const string &key, string *value) const {
+ ssize_t index = mHeaders.indexOfKey(key);
+ if (index < 0) {
+ value->clear();
+ return false;
+ }
+
+ *value = mHeaders.valueAt(index);
+
+ return true;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/MP3Extractor.cpp b/media/libstagefright/MP3Extractor.cpp
new file mode 100644
index 0000000..74f37b1
--- /dev/null
+++ b/media/libstagefright/MP3Extractor.cpp
@@ -0,0 +1,522 @@
+/*
+ * Copyright (C) 2009 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 "MP3Extractor"
+#include <utils/Log.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MP3Extractor.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaErrors.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/Utils.h>
+#include <utils/String8.h>
+
+namespace android {
+
+static bool get_mp3_frame_size(
+ uint32_t header, size_t *frame_size,
+ int *out_sampling_rate = NULL, int *out_channels = NULL,
+ int *out_bitrate = NULL) {
+ *frame_size = 0;
+
+ if (out_sampling_rate) {
+ *out_sampling_rate = 0;
+ }
+
+ if (out_channels) {
+ *out_channels = 0;
+ }
+
+ if (out_bitrate) {
+ *out_bitrate = 0;
+ }
+
+ if ((header & 0xffe00000) != 0xffe00000) {
+ return false;
+ }
+
+ unsigned version = (header >> 19) & 3;
+
+ if (version == 0x01) {
+ return false;
+ }
+
+ unsigned layer = (header >> 17) & 3;
+
+ if (layer == 0x00) {
+ return false;
+ }
+
+ unsigned protection = (header >> 16) & 1;
+
+ unsigned bitrate_index = (header >> 12) & 0x0f;
+
+ if (bitrate_index == 0 || bitrate_index == 0x0f) {
+ // Disallow "free" bitrate.
+ return false;
+ }
+
+ unsigned sampling_rate_index = (header >> 10) & 3;
+
+ if (sampling_rate_index == 3) {
+ return false;
+ }
+
+ static const int kSamplingRateV1[] = { 44100, 48000, 32000 };
+ int sampling_rate = kSamplingRateV1[sampling_rate_index];
+ if (version == 2 /* V2 */) {
+ sampling_rate /= 2;
+ } else if (version == 0 /* V2.5 */) {
+ sampling_rate /= 4;
+ }
+
+ unsigned padding = (header >> 9) & 1;
+
+ if (layer == 3) {
+ // layer I
+
+ static const int kBitrateV1[] = {
+ 32, 64, 96, 128, 160, 192, 224, 256,
+ 288, 320, 352, 384, 416, 448
+ };
+
+ static const int kBitrateV2[] = {
+ 32, 48, 56, 64, 80, 96, 112, 128,
+ 144, 160, 176, 192, 224, 256
+ };
+
+ int bitrate =
+ (version == 3 /* V1 */)
+ ? kBitrateV1[bitrate_index - 1]
+ : kBitrateV2[bitrate_index - 1];
+
+ if (out_bitrate) {
+ *out_bitrate = bitrate;
+ }
+
+ *frame_size = (12000 * bitrate / sampling_rate + padding) * 4;
+ } else {
+ // layer II or III
+
+ static const int kBitrateV1L2[] = {
+ 32, 48, 56, 64, 80, 96, 112, 128,
+ 160, 192, 224, 256, 320, 384
+ };
+
+ static const int kBitrateV1L3[] = {
+ 32, 40, 48, 56, 64, 80, 96, 112,
+ 128, 160, 192, 224, 256, 320
+ };
+
+ static const int kBitrateV2[] = {
+ 8, 16, 24, 32, 40, 48, 56, 64,
+ 80, 96, 112, 128, 144, 160
+ };
+
+ int bitrate;
+ if (version == 3 /* V1 */) {
+ bitrate = (layer == 2 /* L2 */)
+ ? kBitrateV1L2[bitrate_index - 1]
+ : kBitrateV1L3[bitrate_index - 1];
+ } else {
+ // V2 (or 2.5)
+
+ bitrate = kBitrateV2[bitrate_index - 1];
+ }
+
+ if (out_bitrate) {
+ *out_bitrate = bitrate;
+ }
+
+ *frame_size = 144000 * bitrate / sampling_rate + padding;
+ }
+
+ if (out_sampling_rate) {
+ *out_sampling_rate = sampling_rate;
+ }
+
+ if (out_channels) {
+ int channel_mode = (header >> 6) & 3;
+
+ *out_channels = (channel_mode == 3) ? 1 : 2;
+ }
+
+ return true;
+}
+
+static bool Resync(
+ DataSource *source, uint32_t match_header,
+ off_t *inout_pos, uint32_t *out_header) {
+ // Everything must match except for
+ // protection, bitrate, padding, private bits and mode extension.
+ const uint32_t kMask = 0xfffe0ccf;
+
+ const size_t kMaxFrameSize = 4096;
+ uint8_t *buffer = new uint8_t[kMaxFrameSize];
+
+ off_t pos = *inout_pos;
+ size_t buffer_offset = kMaxFrameSize;
+ size_t buffer_length = kMaxFrameSize;
+ bool valid = false;
+ do {
+ if (buffer_offset + 3 >= buffer_length) {
+ if (buffer_length < kMaxFrameSize) {
+ break;
+ }
+
+ pos += buffer_length;
+
+ if (pos >= *inout_pos + 128 * 1024) {
+ // Don't scan forever.
+ LOGV("giving up at offset %ld", pos);
+ break;
+ }
+
+ memmove(buffer, &buffer[buffer_offset], buffer_length - buffer_offset);
+ buffer_length = buffer_length - buffer_offset;
+ buffer_offset = 0;
+
+ ssize_t n = source->read_at(
+ pos, &buffer[buffer_length], kMaxFrameSize - buffer_length);
+
+ if (n <= 0) {
+ break;
+ }
+
+ buffer_length += (size_t)n;
+
+ continue;
+ }
+
+ uint32_t header = U32_AT(&buffer[buffer_offset]);
+
+ if (match_header != 0 && (header & kMask) != (match_header & kMask)) {
+ ++buffer_offset;
+ continue;
+ }
+
+ size_t frame_size;
+ int sample_rate, num_channels, bitrate;
+ if (get_mp3_frame_size(header, &frame_size,
+ &sample_rate, &num_channels, &bitrate)) {
+ LOGV("found possible 1st frame at %ld", pos + buffer_offset);
+
+ // We found what looks like a valid frame,
+ // now find its successors.
+
+ off_t test_pos = pos + buffer_offset + frame_size;
+
+ valid = true;
+ for (int j = 0; j < 3; ++j) {
+ uint8_t tmp[4];
+ if (source->read_at(test_pos, tmp, 4) < 4) {
+ valid = false;
+ break;
+ }
+
+ uint32_t test_header = U32_AT(tmp);
+
+ LOGV("subsequent header is %08x", test_header);
+
+ if ((test_header & kMask) != (header & kMask)) {
+ valid = false;
+ break;
+ }
+
+ size_t test_frame_size;
+ if (!get_mp3_frame_size(test_header, &test_frame_size)) {
+ valid = false;
+ break;
+ }
+
+ LOGV("found subsequent frame #%d at %ld", j + 2, test_pos);
+
+ test_pos += test_frame_size;
+ }
+ }
+
+ if (valid) {
+ *inout_pos = pos + buffer_offset;
+
+ if (out_header != NULL) {
+ *out_header = header;
+ }
+ } else {
+ LOGV("no dice, no valid sequence of frames found.");
+ }
+
+ ++buffer_offset;
+
+ } while (!valid);
+
+ delete[] buffer;
+ buffer = NULL;
+
+ return valid;
+}
+
+class MP3Source : public MediaSource {
+public:
+ MP3Source(
+ const sp<MetaData> &meta, DataSource *source,
+ off_t first_frame_pos, uint32_t fixed_header);
+
+ virtual ~MP3Source();
+
+ virtual status_t start(MetaData *params = NULL);
+ virtual status_t stop();
+
+ virtual sp<MetaData> getFormat();
+
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options = NULL);
+
+private:
+ sp<MetaData> mMeta;
+ DataSource *mDataSource;
+ off_t mFirstFramePos;
+ uint32_t mFixedHeader;
+ off_t mCurrentPos;
+ int64_t mCurrentTimeUs;
+ bool mStarted;
+
+ MediaBufferGroup *mGroup;
+
+ MP3Source(const MP3Source &);
+ MP3Source &operator=(const MP3Source &);
+};
+
+MP3Extractor::MP3Extractor(DataSource *source)
+ : mDataSource(source),
+ mFirstFramePos(-1),
+ mFixedHeader(0) {
+ off_t pos = 0;
+ uint32_t header;
+ bool success = Resync(mDataSource, 0, &pos, &header);
+ assert(success);
+
+ if (success) {
+ mFirstFramePos = pos;
+ mFixedHeader = header;
+
+ size_t frame_size;
+ int sample_rate;
+ int num_channels;
+ int bitrate;
+ get_mp3_frame_size(
+ header, &frame_size, &sample_rate, &num_channels, &bitrate);
+
+ mMeta = new MetaData;
+
+ mMeta->setCString(kKeyMIMEType, "audio/mpeg");
+ mMeta->setInt32(kKeySampleRate, sample_rate);
+ mMeta->setInt32(kKeyBitRate, bitrate);
+ mMeta->setInt32(kKeyChannelCount, num_channels);
+
+ off_t fileSize;
+ if (mDataSource->getSize(&fileSize) == OK) {
+ mMeta->setInt32(
+ kKeyDuration,
+ 8 * (fileSize - mFirstFramePos) / bitrate);
+ mMeta->setInt32(kKeyTimeScale, 1000);
+ }
+ }
+}
+
+MP3Extractor::~MP3Extractor() {
+ delete mDataSource;
+ mDataSource = NULL;
+}
+
+status_t MP3Extractor::countTracks(int *num_tracks) {
+ *num_tracks = mFirstFramePos < 0 ? 0 : 1;
+
+ return OK;
+}
+
+status_t MP3Extractor::getTrack(int index, MediaSource **source) {
+ if (mFirstFramePos < 0 || index != 0) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ *source = new MP3Source(
+ mMeta, mDataSource, mFirstFramePos, mFixedHeader);
+
+ return OK;
+}
+
+sp<MetaData> MP3Extractor::getTrackMetaData(int index) {
+ if (mFirstFramePos < 0 || index != 0) {
+ return NULL;
+ }
+
+ return mMeta;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+MP3Source::MP3Source(
+ const sp<MetaData> &meta, DataSource *source,
+ off_t first_frame_pos, uint32_t fixed_header)
+ : mMeta(meta),
+ mDataSource(source),
+ mFirstFramePos(first_frame_pos),
+ mFixedHeader(fixed_header),
+ mCurrentPos(0),
+ mCurrentTimeUs(0),
+ mStarted(false),
+ mGroup(NULL) {
+}
+
+MP3Source::~MP3Source() {
+ if (mStarted) {
+ stop();
+ }
+}
+
+status_t MP3Source::start(MetaData *) {
+ assert(!mStarted);
+
+ mGroup = new MediaBufferGroup;
+
+ const size_t kMaxFrameSize = 32768;
+ mGroup->add_buffer(new MediaBuffer(kMaxFrameSize));
+
+ mCurrentPos = mFirstFramePos;
+ mCurrentTimeUs = 0;
+
+ mStarted = true;
+
+ return OK;
+}
+
+status_t MP3Source::stop() {
+ assert(mStarted);
+
+ delete mGroup;
+ mGroup = NULL;
+
+ mStarted = false;
+
+ return OK;
+}
+
+sp<MetaData> MP3Source::getFormat() {
+ return mMeta;
+}
+
+status_t MP3Source::read(
+ MediaBuffer **out, const ReadOptions *options) {
+ *out = NULL;
+
+ int64_t seekTimeUs;
+ if (options != NULL && options->getSeekTo(&seekTimeUs)) {
+ int32_t bitrate;
+ if (!mMeta->findInt32(kKeyBitRate, &bitrate)) {
+ // bitrate is in kbits/sec.
+ LOGI("no bitrate");
+
+ return ERROR_UNSUPPORTED;
+ }
+
+ mCurrentTimeUs = seekTimeUs;
+ mCurrentPos = mFirstFramePos + seekTimeUs * bitrate / 1000000 * 125;
+ }
+
+ MediaBuffer *buffer;
+ status_t err = mGroup->acquire_buffer(&buffer);
+ if (err != OK) {
+ return err;
+ }
+
+ size_t frame_size;
+ for (;;) {
+ ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), 4);
+ if (n < 4) {
+ buffer->release();
+ buffer = NULL;
+
+ return ERROR_END_OF_STREAM;
+ }
+
+ uint32_t header = U32_AT((const uint8_t *)buffer->data());
+
+ if (get_mp3_frame_size(header, &frame_size)) {
+ break;
+ }
+
+ // Lost sync.
+ LOGW("lost sync!\n");
+
+ off_t pos = mCurrentPos;
+ if (!Resync(mDataSource, mFixedHeader, &pos, NULL)) {
+ LOGE("Unable to resync. Signalling end of stream.");
+
+ buffer->release();
+ buffer = NULL;
+
+ return ERROR_END_OF_STREAM;
+ }
+
+ mCurrentPos = pos;
+
+ // Try again with the new position.
+ }
+
+ assert(frame_size <= buffer->size());
+
+ ssize_t n = mDataSource->read_at(mCurrentPos, buffer->data(), frame_size);
+ if (n < (ssize_t)frame_size) {
+ buffer->release();
+ buffer = NULL;
+
+ return ERROR_END_OF_STREAM;
+ }
+
+ buffer->set_range(0, frame_size);
+
+ buffer->meta_data()->setInt32(kKeyTimeUnits, mCurrentTimeUs / 1000);
+ buffer->meta_data()->setInt32(kKeyTimeScale, 1000);
+
+ mCurrentPos += frame_size;
+ mCurrentTimeUs += 1152 * 1000000 / 44100;
+
+ *out = buffer;
+
+ return OK;
+}
+
+bool SniffMP3(DataSource *source, String8 *mimeType, float *confidence) {
+ off_t pos = 0;
+ uint32_t header;
+ if (!Resync(source, 0, &pos, &header)) {
+ return false;
+ }
+
+ *mimeType = "audio/mpeg";
+ *confidence = 0.3f;
+
+ return true;
+}
+
+} // namespace android
diff --git a/media/libstagefright/MPEG4Extractor.cpp b/media/libstagefright/MPEG4Extractor.cpp
new file mode 100644
index 0000000..caaec06
--- /dev/null
+++ b/media/libstagefright/MPEG4Extractor.cpp
@@ -0,0 +1,937 @@
+/*
+ * Copyright (C) 2009 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_TAG "MPEG4Extractor"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <ctype.h>
+#include <stdint.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MPEG4Extractor.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/SampleTable.h>
+#include <media/stagefright/Utils.h>
+#include <utils/String8.h>
+
+namespace android {
+
+class MPEG4Source : public MediaSource {
+public:
+ // Caller retains ownership of both "dataSource" and "sampleTable".
+ MPEG4Source(const sp<MetaData> &format, DataSource *dataSource,
+ SampleTable *sampleTable);
+
+ virtual ~MPEG4Source();
+
+ virtual status_t start(MetaData *params = NULL);
+ virtual status_t stop();
+
+ virtual sp<MetaData> getFormat();
+
+ virtual status_t read(
+ MediaBuffer **buffer, const ReadOptions *options = NULL);
+
+private:
+ sp<MetaData> mFormat;
+ DataSource *mDataSource;
+ int32_t mTimescale;
+ SampleTable *mSampleTable;
+ uint32_t mCurrentSampleIndex;
+
+ bool mIsAVC;
+ bool mStarted;
+
+ MediaBufferGroup *mGroup;
+
+ MediaBuffer *mBuffer;
+ size_t mBufferOffset;
+ size_t mBufferSizeRemaining;
+
+ bool mNeedsNALFraming;
+
+ MPEG4Source(const MPEG4Source &);
+ MPEG4Source &operator=(const MPEG4Source &);
+};
+
+static void hexdump(const void *_data, size_t size) {
+ const uint8_t *data = (const uint8_t *)_data;
+ size_t offset = 0;
+ while (offset < size) {
+ printf("0x%04x ", offset);
+
+ size_t n = size - offset;
+ if (n > 16) {
+ n = 16;
+ }
+
+ for (size_t i = 0; i < 16; ++i) {
+ if (i == 8) {
+ printf(" ");
+ }
+
+ if (offset + i < size) {
+ printf("%02x ", data[offset + i]);
+ } else {
+ printf(" ");
+ }
+ }
+
+ printf(" ");
+
+ for (size_t i = 0; i < n; ++i) {
+ if (isprint(data[offset + i])) {
+ printf("%c", data[offset + i]);
+ } else {
+ printf(".");
+ }
+ }
+
+ printf("\n");
+
+ offset += 16;
+ }
+}
+
+static const char *const FourCC2MIME(uint32_t fourcc) {
+ switch (fourcc) {
+ case FOURCC('m', 'p', '4', 'a'):
+ return "audio/mp4a-latm";
+
+ case FOURCC('s', 'a', 'm', 'r'):
+ return "audio/3gpp";
+
+ case FOURCC('m', 'p', '4', 'v'):
+ return "video/mp4v-es";
+
+ case FOURCC('s', '2', '6', '3'):
+ return "video/3gpp";
+
+ case FOURCC('a', 'v', 'c', '1'):
+ return "video/avc";
+
+ default:
+ assert(!"should not be here.");
+ return NULL;
+ }
+}
+
+MPEG4Extractor::MPEG4Extractor(DataSource *source)
+ : mDataSource(source),
+ mHaveMetadata(false),
+ mFirstTrack(NULL),
+ mLastTrack(NULL) {
+}
+
+MPEG4Extractor::~MPEG4Extractor() {
+ Track *track = mFirstTrack;
+ while (track) {
+ Track *next = track->next;
+
+ delete track->sampleTable;
+ track->sampleTable = NULL;
+
+ delete track;
+ track = next;
+ }
+ mFirstTrack = mLastTrack = NULL;
+
+ delete mDataSource;
+ mDataSource = NULL;
+}
+
+status_t MPEG4Extractor::countTracks(int *num_tracks) {
+ status_t err;
+ if ((err = readMetaData()) != OK) {
+ return err;
+ }
+
+ *num_tracks = 0;
+ Track *track = mFirstTrack;
+ while (track) {
+ ++*num_tracks;
+ track = track->next;
+ }
+
+ return OK;
+}
+
+sp<MetaData> MPEG4Extractor::getTrackMetaData(int index) {
+ if (index < 0) {
+ return NULL;
+ }
+
+ status_t err;
+ if ((err = readMetaData()) != OK) {
+ return NULL;
+ }
+
+ Track *track = mFirstTrack;
+ while (index > 0) {
+ if (track == NULL) {
+ return NULL;
+ }
+
+ track = track->next;
+ --index;
+ }
+
+ return track->meta;
+}
+
+status_t MPEG4Extractor::readMetaData() {
+ if (mHaveMetadata) {
+ return OK;
+ }
+
+ off_t offset = 0;
+ status_t err;
+ while ((err = parseChunk(&offset, 0)) == OK) {
+ }
+
+ if (mHaveMetadata) {
+ return OK;
+ }
+
+ return err;
+}
+
+static void MakeFourCCString(uint32_t x, char *s) {
+ s[0] = x >> 24;
+ s[1] = (x >> 16) & 0xff;
+ s[2] = (x >> 8) & 0xff;
+ s[3] = x & 0xff;
+ s[4] = '\0';
+}
+
+status_t MPEG4Extractor::parseChunk(off_t *offset, int depth) {
+ uint32_t hdr[2];
+ if (mDataSource->read_at(*offset, hdr, 8) < 8) {
+ return ERROR_IO;
+ }
+ uint64_t chunk_size = ntohl(hdr[0]);
+ uint32_t chunk_type = ntohl(hdr[1]);
+ off_t data_offset = *offset + 8;
+
+ if (chunk_size == 1) {
+ if (mDataSource->read_at(*offset + 8, &chunk_size, 8) < 8) {
+ return ERROR_IO;
+ }
+ chunk_size = ntoh64(chunk_size);
+ data_offset += 8;
+ }
+
+ char chunk[5];
+ MakeFourCCString(chunk_type, chunk);
+
+#if 0
+ static const char kWhitespace[] = " ";
+ const char *indent = &kWhitespace[sizeof(kWhitespace) - 1 - 2 * depth];
+ printf("%sfound chunk '%s' of size %lld\n", indent, chunk, chunk_size);
+
+ char buffer[256];
+ if (chunk_size <= sizeof(buffer)) {
+ if (mDataSource->read_at(*offset, buffer, chunk_size) < chunk_size) {
+ return ERROR_IO;
+ }
+
+ hexdump(buffer, chunk_size);
+ }
+#endif
+
+ off_t chunk_data_size = *offset + chunk_size - data_offset;
+
+ switch(chunk_type) {
+ case FOURCC('m', 'o', 'o', 'v'):
+ case FOURCC('t', 'r', 'a', 'k'):
+ case FOURCC('m', 'd', 'i', 'a'):
+ case FOURCC('m', 'i', 'n', 'f'):
+ case FOURCC('d', 'i', 'n', 'f'):
+ case FOURCC('s', 't', 'b', 'l'):
+ case FOURCC('m', 'v', 'e', 'x'):
+ case FOURCC('m', 'o', 'o', 'f'):
+ case FOURCC('t', 'r', 'a', 'f'):
+ case FOURCC('m', 'f', 'r', 'a'):
+ case FOURCC('s', 'k', 'i' ,'p'):
+ {
+ off_t stop_offset = *offset + chunk_size;
+ *offset = data_offset;
+ while (*offset < stop_offset) {
+ status_t err = parseChunk(offset, depth + 1);
+ if (err != OK) {
+ return err;
+ }
+ }
+ assert(*offset == stop_offset);
+
+ if (chunk_type == FOURCC('m', 'o', 'o', 'v')) {
+ mHaveMetadata = true;
+
+ return UNKNOWN_ERROR; // Return a dummy error.
+ }
+ break;
+ }
+
+ case FOURCC('t', 'k', 'h', 'd'):
+ {
+ assert(chunk_data_size >= 4);
+
+ uint8_t version;
+ if (mDataSource->read_at(data_offset, &version, 1) < 1) {
+ return ERROR_IO;
+ }
+
+ uint64_t ctime, mtime, duration;
+ int32_t id;
+ uint32_t width, height;
+
+ if (version == 1) {
+ if (chunk_data_size != 36 + 60) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t buffer[36 + 60];
+ if (mDataSource->read_at(
+ data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
+ return ERROR_IO;
+ }
+
+ ctime = U64_AT(&buffer[4]);
+ mtime = U64_AT(&buffer[12]);
+ id = U32_AT(&buffer[20]);
+ duration = U64_AT(&buffer[28]);
+ width = U32_AT(&buffer[88]);
+ height = U32_AT(&buffer[92]);
+ } else if (version == 0) {
+ if (chunk_data_size != 24 + 60) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t buffer[24 + 60];
+ if (mDataSource->read_at(
+ data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
+ return ERROR_IO;
+ }
+ ctime = U32_AT(&buffer[4]);
+ mtime = U32_AT(&buffer[8]);
+ id = U32_AT(&buffer[12]);
+ duration = U32_AT(&buffer[20]);
+ width = U32_AT(&buffer[76]);
+ height = U32_AT(&buffer[80]);
+ }
+
+ Track *track = new Track;
+ track->next = NULL;
+ if (mLastTrack) {
+ mLastTrack->next = track;
+ } else {
+ mFirstTrack = track;
+ }
+ mLastTrack = track;
+
+ track->meta = new MetaData;
+ track->timescale = 0;
+ track->sampleTable = new SampleTable(mDataSource);
+ track->meta->setCString(kKeyMIMEType, "application/octet-stream");
+
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('m', 'd', 'h', 'd'):
+ {
+ if (chunk_data_size < 4) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t version;
+ if (mDataSource->read_at(
+ data_offset, &version, sizeof(version))
+ < (ssize_t)sizeof(version)) {
+ return ERROR_IO;
+ }
+
+ off_t timescale_offset;
+
+ if (version == 1) {
+ timescale_offset = data_offset + 4 + 16;
+ } else if (version == 0) {
+ timescale_offset = data_offset + 4 + 8;
+ } else {
+ return ERROR_IO;
+ }
+
+ uint32_t timescale;
+ if (mDataSource->read_at(
+ timescale_offset, &timescale, sizeof(timescale))
+ < (ssize_t)sizeof(timescale)) {
+ return ERROR_IO;
+ }
+
+ mLastTrack->timescale = ntohl(timescale);
+ mLastTrack->meta->setInt32(kKeyTimeScale, mLastTrack->timescale);
+
+ int64_t duration;
+ if (version == 1) {
+ if (mDataSource->read_at(
+ timescale_offset + 4, &duration, sizeof(duration))
+ < (ssize_t)sizeof(duration)) {
+ return ERROR_IO;
+ }
+ duration = ntoh64(duration);
+ } else {
+ int32_t duration32;
+ if (mDataSource->read_at(
+ timescale_offset + 4, &duration32, sizeof(duration32))
+ < (ssize_t)sizeof(duration32)) {
+ return ERROR_IO;
+ }
+ duration = ntohl(duration32);
+ }
+ mLastTrack->meta->setInt32(kKeyDuration, duration);
+
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('h', 'd', 'l', 'r'):
+ {
+ if (chunk_data_size < 25) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t buffer[24];
+ if (mDataSource->read_at(data_offset, buffer, 24) < 24) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(buffer) != 0) {
+ // Should be version 0, flags 0.
+ return ERROR_MALFORMED;
+ }
+
+ if (U32_AT(&buffer[4]) != 0) {
+ // pre_defined should be 0.
+ return ERROR_MALFORMED;
+ }
+
+ mHandlerType = U32_AT(&buffer[8]);
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('s', 't', 's', 'd'):
+ {
+ if (chunk_data_size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t buffer[8];
+ assert(chunk_data_size >= (off_t)sizeof(buffer));
+ if (mDataSource->read_at(
+ data_offset, buffer, 8) < 8) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(buffer) != 0) {
+ // Should be version 0, flags 0.
+ return ERROR_MALFORMED;
+ }
+
+ uint32_t entry_count = U32_AT(&buffer[4]);
+
+ if (entry_count > 1) {
+ // For now we only support a single type of media per track.
+ return ERROR_UNSUPPORTED;
+ }
+
+ off_t stop_offset = *offset + chunk_size;
+ *offset = data_offset + 8;
+ for (uint32_t i = 0; i < entry_count; ++i) {
+ status_t err = parseChunk(offset, depth + 1);
+ if (err != OK) {
+ return err;
+ }
+ }
+ assert(*offset == stop_offset);
+ break;
+ }
+
+ case FOURCC('m', 'p', '4', 'a'):
+ case FOURCC('s', 'a', 'm', 'r'):
+ {
+ if (mHandlerType != FOURCC('s', 'o', 'u', 'n')) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t buffer[8 + 20];
+ if (chunk_data_size < (ssize_t)sizeof(buffer)) {
+ // Basic AudioSampleEntry size.
+ return ERROR_MALFORMED;
+ }
+
+ if (mDataSource->read_at(
+ data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
+ return ERROR_IO;
+ }
+
+ uint16_t data_ref_index = U16_AT(&buffer[6]);
+ uint16_t num_channels = U16_AT(&buffer[16]);
+
+ if (!strcasecmp("audio/3gpp", FourCC2MIME(chunk_type))) {
+ // AMR audio is always mono.
+ num_channels = 1;
+ }
+
+ uint16_t sample_size = U16_AT(&buffer[18]);
+ uint32_t sample_rate = U32_AT(&buffer[24]) >> 16;
+
+ printf("*** coding='%s' %d channels, size %d, rate %d\n",
+ chunk, num_channels, sample_size, sample_rate);
+
+ mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type));
+ mLastTrack->meta->setInt32(kKeyChannelCount, num_channels);
+ mLastTrack->meta->setInt32(kKeySampleRate, sample_rate);
+
+ off_t stop_offset = *offset + chunk_size;
+ *offset = data_offset + sizeof(buffer);
+ while (*offset < stop_offset) {
+ status_t err = parseChunk(offset, depth + 1);
+ if (err != OK) {
+ return err;
+ }
+ }
+ assert(*offset == stop_offset);
+ break;
+ }
+
+ case FOURCC('m', 'p', '4', 'v'):
+ case FOURCC('s', '2', '6', '3'):
+ case FOURCC('a', 'v', 'c', '1'):
+ {
+ if (mHandlerType != FOURCC('v', 'i', 'd', 'e')) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t buffer[78];
+ if (chunk_data_size < (ssize_t)sizeof(buffer)) {
+ // Basic VideoSampleEntry size.
+ return ERROR_MALFORMED;
+ }
+
+ if (mDataSource->read_at(
+ data_offset, buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
+ return ERROR_IO;
+ }
+
+ uint16_t data_ref_index = U16_AT(&buffer[6]);
+ uint16_t width = U16_AT(&buffer[6 + 18]);
+ uint16_t height = U16_AT(&buffer[6 + 20]);
+
+ printf("*** coding='%s' width=%d height=%d\n",
+ chunk, width, height);
+
+ mLastTrack->meta->setCString(kKeyMIMEType, FourCC2MIME(chunk_type));
+ mLastTrack->meta->setInt32(kKeyWidth, width);
+ mLastTrack->meta->setInt32(kKeyHeight, height);
+
+ off_t stop_offset = *offset + chunk_size;
+ *offset = data_offset + sizeof(buffer);
+ while (*offset < stop_offset) {
+ status_t err = parseChunk(offset, depth + 1);
+ if (err != OK) {
+ return err;
+ }
+ }
+ assert(*offset == stop_offset);
+ break;
+ }
+
+ case FOURCC('s', 't', 'c', 'o'):
+ case FOURCC('c', 'o', '6', '4'):
+ {
+ status_t err =
+ mLastTrack->sampleTable->setChunkOffsetParams(
+ chunk_type, data_offset, chunk_data_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('s', 't', 's', 'c'):
+ {
+ status_t err =
+ mLastTrack->sampleTable->setSampleToChunkParams(
+ data_offset, chunk_data_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('s', 't', 's', 'z'):
+ case FOURCC('s', 't', 'z', '2'):
+ {
+ status_t err =
+ mLastTrack->sampleTable->setSampleSizeParams(
+ chunk_type, data_offset, chunk_data_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('s', 't', 't', 's'):
+ {
+ status_t err =
+ mLastTrack->sampleTable->setTimeToSampleParams(
+ data_offset, chunk_data_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('s', 't', 's', 's'):
+ {
+ status_t err =
+ mLastTrack->sampleTable->setSyncSampleParams(
+ data_offset, chunk_data_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('e', 's', 'd', 's'):
+ {
+ if (chunk_data_size < 4) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t buffer[256];
+ if (chunk_data_size > (off_t)sizeof(buffer)) {
+ return ERROR_BUFFER_TOO_SMALL;
+ }
+
+ if (mDataSource->read_at(
+ data_offset, buffer, chunk_data_size) < chunk_data_size) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(buffer) != 0) {
+ // Should be version 0, flags 0.
+ return ERROR_MALFORMED;
+ }
+
+ mLastTrack->meta->setData(
+ kKeyESDS, kTypeESDS, &buffer[4], chunk_data_size - 4);
+
+ *offset += chunk_size;
+ break;
+ }
+
+ case FOURCC('a', 'v', 'c', 'C'):
+ {
+ char buffer[256];
+ if (chunk_data_size > (off_t)sizeof(buffer)) {
+ return ERROR_BUFFER_TOO_SMALL;
+ }
+
+ if (mDataSource->read_at(
+ data_offset, buffer, chunk_data_size) < chunk_data_size) {
+ return ERROR_IO;
+ }
+
+ mLastTrack->meta->setData(
+ kKeyAVCC, kTypeAVCC, buffer, chunk_data_size);
+
+ *offset += chunk_size;
+ break;
+ }
+
+ default:
+ {
+ *offset += chunk_size;
+ break;
+ }
+ }
+
+ return OK;
+}
+
+status_t MPEG4Extractor::getTrack(int index, MediaSource **source) {
+ *source = NULL;
+
+ if (index < 0) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ status_t err;
+ if ((err = readMetaData()) != OK) {
+ return err;
+ }
+
+ Track *track = mFirstTrack;
+ while (index > 0) {
+ if (track == NULL) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ track = track->next;
+ --index;
+ }
+
+ *source = new MPEG4Source(
+ track->meta, mDataSource, track->sampleTable);
+
+ return OK;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+MPEG4Source::MPEG4Source(
+ const sp<MetaData> &format,
+ DataSource *dataSource, SampleTable *sampleTable)
+ : mFormat(format),
+ mDataSource(dataSource),
+ mTimescale(0),
+ mSampleTable(sampleTable),
+ mCurrentSampleIndex(0),
+ mIsAVC(false),
+ mStarted(false),
+ mGroup(NULL),
+ mBuffer(NULL),
+ mBufferOffset(0),
+ mBufferSizeRemaining(0),
+ mNeedsNALFraming(false) {
+ const char *mime;
+ bool success = mFormat->findCString(kKeyMIMEType, &mime);
+ assert(success);
+
+ success = mFormat->findInt32(kKeyTimeScale, &mTimescale);
+ assert(success);
+
+ mIsAVC = !strcasecmp(mime, "video/avc");
+}
+
+MPEG4Source::~MPEG4Source() {
+ if (mStarted) {
+ stop();
+ }
+}
+
+status_t MPEG4Source::start(MetaData *params) {
+ assert(!mStarted);
+
+ int32_t val;
+ if (mIsAVC && params && params->findInt32(kKeyNeedsNALFraming, &val)
+ && val != 0) {
+ mNeedsNALFraming = true;
+ } else {
+ mNeedsNALFraming = false;
+ }
+
+ mGroup = new MediaBufferGroup;
+
+ size_t max_size;
+ status_t err = mSampleTable->getMaxSampleSize(&max_size);
+ assert(err == OK);
+
+ // Add padding for de-framing of AVC content just in case.
+ mGroup->add_buffer(new MediaBuffer(max_size + 2));
+
+ mStarted = true;
+
+ return OK;
+}
+
+status_t MPEG4Source::stop() {
+ assert(mStarted);
+
+ if (mBuffer != NULL) {
+ mBuffer->release();
+ mBuffer = NULL;
+ }
+
+ delete mGroup;
+ mGroup = NULL;
+
+ mStarted = false;
+ mCurrentSampleIndex = 0;
+
+ return OK;
+}
+
+sp<MetaData> MPEG4Source::getFormat() {
+ return mFormat;
+}
+
+status_t MPEG4Source::read(
+ MediaBuffer **out, const ReadOptions *options) {
+ assert(mStarted);
+
+ *out = NULL;
+
+ int64_t seekTimeUs;
+ if (options && options->getSeekTo(&seekTimeUs)) {
+ uint32_t sampleIndex;
+ status_t err = mSampleTable->findClosestSample(
+ seekTimeUs * mTimescale / 1000000,
+ &sampleIndex, SampleTable::kSyncSample_Flag);
+
+ if (err != OK) {
+ return err;
+ }
+
+ mCurrentSampleIndex = sampleIndex;
+ if (mBuffer != NULL) {
+ mBuffer->release();
+ mBuffer = NULL;
+ }
+
+ // fall through
+ }
+
+ if (mBuffer == NULL) {
+ off_t offset;
+ size_t size;
+ status_t err = mSampleTable->getSampleOffsetAndSize(
+ mCurrentSampleIndex, &offset, &size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ uint32_t dts;
+ err = mSampleTable->getDecodingTime(mCurrentSampleIndex, &dts);
+
+ if (err != OK) {
+ return err;
+ }
+
+ err = mGroup->acquire_buffer(&mBuffer);
+ if (err != OK) {
+ assert(mBuffer == NULL);
+ return err;
+ }
+
+ assert(mBuffer->size() + 2 >= size);
+
+ ssize_t num_bytes_read =
+ mDataSource->read_at(offset, (uint8_t *)mBuffer->data() + 2, size);
+
+ if (num_bytes_read < (ssize_t)size) {
+ mBuffer->release();
+ mBuffer = NULL;
+
+ return err;
+ }
+
+ mBuffer->set_range(2, size);
+ mBuffer->meta_data()->clear();
+ mBuffer->meta_data()->setInt32(kKeyTimeUnits, dts);
+ mBuffer->meta_data()->setInt32(kKeyTimeScale, mTimescale);
+
+ ++mCurrentSampleIndex;
+
+ mBufferOffset = 2;
+ mBufferSizeRemaining = size;
+ }
+
+ if (!mIsAVC) {
+ *out = mBuffer;
+ mBuffer = NULL;
+
+ return OK;
+ }
+
+ uint8_t *data = (uint8_t *)mBuffer->data() + mBufferOffset;
+ assert(mBufferSizeRemaining >= 2);
+
+ size_t nal_length = (data[0] << 8) | data[1];
+ assert(mBufferSizeRemaining >= 2 + nal_length);
+
+ if (mNeedsNALFraming) {
+ // Insert marker.
+ data[-2] = data[-1] = data[0] = 0;
+ data[1] = 1;
+
+ mBuffer->set_range(mBufferOffset - 2, nal_length + 4);
+ } else {
+ mBuffer->set_range(mBufferOffset + 2, nal_length);
+ }
+
+ mBufferOffset += nal_length + 2;
+ mBufferSizeRemaining -= nal_length + 2;
+
+ if (mBufferSizeRemaining > 0) {
+ *out = mBuffer->clone();
+ } else {
+ *out = mBuffer;
+ mBuffer = NULL;
+ }
+
+ return OK;
+}
+
+bool SniffMPEG4(DataSource *source, String8 *mimeType, float *confidence) {
+ uint8_t header[8];
+
+ ssize_t n = source->read_at(4, header, sizeof(header));
+ if (n < (ssize_t)sizeof(header)) {
+ return false;
+ }
+
+ if (!memcmp(header, "ftyp3gp", 7) || !memcmp(header, "ftypmp42", 8)
+ || !memcmp(header, "ftypisom", 8) || !memcmp(header, "ftypM4V ", 8)) {
+ *mimeType = "video/mp4";
+ *confidence = 0.1;
+
+ return true;
+ }
+
+ return false;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/MPEG4Writer.cpp b/media/libstagefright/MPEG4Writer.cpp
new file mode 100644
index 0000000..6bdf282
--- /dev/null
+++ b/media/libstagefright/MPEG4Writer.cpp
@@ -0,0 +1,641 @@
+/*
+ * Copyright (C) 2009 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 <arpa/inet.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <ctype.h>
+#include <pthread.h>
+
+#include <media/stagefright/MPEG4Writer.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MediaSource.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+class MPEG4Writer::Track {
+public:
+ Track(MPEG4Writer *owner, const sp<MetaData> &meta, MediaSource *source);
+ ~Track();
+
+ void start();
+ void stop();
+
+ int64_t getDuration() const;
+ void writeTrackHeader(int32_t trackID);
+
+private:
+ MPEG4Writer *mOwner;
+ sp<MetaData> mMeta;
+ MediaSource *mSource;
+ volatile bool mDone;
+
+ pthread_t mThread;
+
+ struct SampleInfo {
+ size_t size;
+ off_t offset;
+ int64_t timestamp;
+ };
+ List<SampleInfo> mSampleInfos;
+
+ void *mCodecSpecificData;
+ size_t mCodecSpecificDataSize;
+
+ static void *ThreadWrapper(void *me);
+ void threadEntry();
+
+ Track(const Track &);
+ Track &operator=(const Track &);
+};
+
+MPEG4Writer::MPEG4Writer(const char *filename)
+ : mFile(fopen(filename, "wb")),
+ mOffset(0),
+ mMdatOffset(0) {
+ assert(mFile != NULL);
+}
+
+MPEG4Writer::~MPEG4Writer() {
+ stop();
+
+ for (List<Track *>::iterator it = mTracks.begin();
+ it != mTracks.end(); ++it) {
+ delete *it;
+ }
+ mTracks.clear();
+}
+
+void MPEG4Writer::addSource(const sp<MetaData> &meta, MediaSource *source) {
+ Track *track = new Track(this, meta, source);
+ mTracks.push_back(track);
+}
+
+void MPEG4Writer::start() {
+ if (mFile == NULL) {
+ return;
+ }
+
+ beginBox("ftyp");
+ writeFourcc("isom");
+ writeInt32(0);
+ writeFourcc("isom");
+ endBox();
+
+ mMdatOffset = mOffset;
+ write("\x00\x00\x00\x01mdat????????", 16);
+
+ for (List<Track *>::iterator it = mTracks.begin();
+ it != mTracks.end(); ++it) {
+ (*it)->start();
+ }
+}
+
+void MPEG4Writer::stop() {
+ if (mFile == NULL) {
+ return;
+ }
+
+ int64_t max_duration = 0;
+ for (List<Track *>::iterator it = mTracks.begin();
+ it != mTracks.end(); ++it) {
+ (*it)->stop();
+
+ int64_t duration = (*it)->getDuration();
+ if (duration > max_duration) {
+ max_duration = duration;
+ }
+ }
+
+ // Fix up the size of the 'mdat' chunk.
+ fseek(mFile, mMdatOffset + 8, SEEK_SET);
+ int64_t size = mOffset - mMdatOffset;
+ size = hton64(size);
+ fwrite(&size, 1, 8, mFile);
+ fseek(mFile, mOffset, SEEK_SET);
+
+ time_t now = time(NULL);
+
+ beginBox("moov");
+
+ beginBox("mvhd");
+ writeInt32(0); // version=0, flags=0
+ writeInt32(now); // creation time
+ writeInt32(now); // modification time
+ writeInt32(1000); // timescale
+ writeInt32(max_duration);
+ writeInt32(0x10000); // rate
+ writeInt16(0x100); // volume
+ writeInt16(0); // reserved
+ writeInt32(0); // reserved
+ writeInt32(0); // reserved
+ writeInt32(0x10000); // matrix
+ writeInt32(0);
+ writeInt32(0);
+ writeInt32(0);
+ writeInt32(0x10000);
+ writeInt32(0);
+ writeInt32(0);
+ writeInt32(0);
+ writeInt32(0x40000000);
+ writeInt32(0); // predefined
+ writeInt32(0); // predefined
+ writeInt32(0); // predefined
+ writeInt32(0); // predefined
+ writeInt32(0); // predefined
+ writeInt32(0); // predefined
+ writeInt32(mTracks.size() + 1); // nextTrackID
+ endBox(); // mvhd
+
+ int32_t id = 1;
+ for (List<Track *>::iterator it = mTracks.begin();
+ it != mTracks.end(); ++it, ++id) {
+ (*it)->writeTrackHeader(id);
+ }
+ endBox(); // moov
+
+ assert(mBoxes.empty());
+
+ fclose(mFile);
+ mFile = NULL;
+}
+
+off_t MPEG4Writer::addSample(MediaBuffer *buffer) {
+ Mutex::Autolock autoLock(mLock);
+
+ off_t old_offset = mOffset;
+
+ fwrite((const uint8_t *)buffer->data() + buffer->range_offset(),
+ 1, buffer->range_length(), mFile);
+
+ mOffset += buffer->range_length();
+
+ return old_offset;
+}
+
+void MPEG4Writer::beginBox(const char *fourcc) {
+ assert(strlen(fourcc) == 4);
+
+ mBoxes.push_back(mOffset);
+
+ writeInt32(0);
+ writeFourcc(fourcc);
+}
+
+void MPEG4Writer::endBox() {
+ assert(!mBoxes.empty());
+
+ off_t offset = *--mBoxes.end();
+ mBoxes.erase(--mBoxes.end());
+
+ fseek(mFile, offset, SEEK_SET);
+ writeInt32(mOffset - offset);
+ mOffset -= 4;
+ fseek(mFile, mOffset, SEEK_SET);
+}
+
+void MPEG4Writer::writeInt8(int8_t x) {
+ fwrite(&x, 1, 1, mFile);
+ ++mOffset;
+}
+
+void MPEG4Writer::writeInt16(int16_t x) {
+ x = htons(x);
+ fwrite(&x, 1, 2, mFile);
+ mOffset += 2;
+}
+
+void MPEG4Writer::writeInt32(int32_t x) {
+ x = htonl(x);
+ fwrite(&x, 1, 4, mFile);
+ mOffset += 4;
+}
+
+void MPEG4Writer::writeInt64(int64_t x) {
+ x = hton64(x);
+ fwrite(&x, 1, 8, mFile);
+ mOffset += 8;
+}
+
+void MPEG4Writer::writeCString(const char *s) {
+ size_t n = strlen(s);
+
+ fwrite(s, 1, n + 1, mFile);
+ mOffset += n + 1;
+}
+
+void MPEG4Writer::writeFourcc(const char *s) {
+ assert(strlen(s) == 4);
+ fwrite(s, 1, 4, mFile);
+ mOffset += 4;
+}
+
+void MPEG4Writer::write(const void *data, size_t size) {
+ fwrite(data, 1, size, mFile);
+ mOffset += size;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+MPEG4Writer::Track::Track(
+ MPEG4Writer *owner, const sp<MetaData> &meta, MediaSource *source)
+ : mOwner(owner),
+ mMeta(meta),
+ mSource(source),
+ mDone(false),
+ mCodecSpecificData(NULL),
+ mCodecSpecificDataSize(0) {
+}
+
+MPEG4Writer::Track::~Track() {
+ stop();
+
+ if (mCodecSpecificData != NULL) {
+ free(mCodecSpecificData);
+ mCodecSpecificData = NULL;
+ }
+}
+
+void MPEG4Writer::Track::start() {
+ mSource->start();
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ mDone = false;
+
+ int err = pthread_create(&mThread, &attr, ThreadWrapper, this);
+ assert(err == 0);
+
+ pthread_attr_destroy(&attr);
+}
+
+void MPEG4Writer::Track::stop() {
+ if (mDone) {
+ return;
+ }
+
+ mDone = true;
+
+ void *dummy;
+ pthread_join(mThread, &dummy);
+
+ mSource->stop();
+}
+
+// static
+void *MPEG4Writer::Track::ThreadWrapper(void *me) {
+ Track *track = static_cast<Track *>(me);
+
+ track->threadEntry();
+
+ return NULL;
+}
+
+void MPEG4Writer::Track::threadEntry() {
+ bool is_mpeg4 = false;
+ sp<MetaData> meta = mSource->getFormat();
+ const char *mime;
+ meta->findCString(kKeyMIMEType, &mime);
+ is_mpeg4 = !strcasecmp(mime, "video/mp4v-es");
+
+ MediaBuffer *buffer;
+ while (!mDone && mSource->read(&buffer) == OK) {
+ if (buffer->range_length() == 0) {
+ buffer->release();
+ buffer = NULL;
+
+ continue;
+ }
+
+ if (mCodecSpecificData == NULL && is_mpeg4) {
+ const uint8_t *data =
+ (const uint8_t *)buffer->data() + buffer->range_offset();
+
+ const size_t size = buffer->range_length();
+
+ size_t offset = 0;
+ while (offset + 3 < size) {
+ if (data[offset] == 0x00 && data[offset + 1] == 0x00
+ && data[offset + 2] == 0x01 && data[offset + 3] == 0xb6) {
+ break;
+ }
+
+ ++offset;
+ }
+
+ assert(offset + 3 < size);
+
+ mCodecSpecificDataSize = offset;
+ mCodecSpecificData = malloc(offset);
+ memcpy(mCodecSpecificData, data, offset);
+
+ buffer->set_range(buffer->range_offset() + offset, size - offset);
+ }
+
+ off_t offset = mOwner->addSample(buffer);
+
+ SampleInfo info;
+ info.size = buffer->range_length();
+ info.offset = offset;
+
+ int32_t units, scale;
+ bool success =
+ buffer->meta_data()->findInt32(kKeyTimeUnits, &units);
+ assert(success);
+ success =
+ buffer->meta_data()->findInt32(kKeyTimeScale, &scale);
+ assert(success);
+
+ info.timestamp = (int64_t)units * 1000 / scale;
+
+ mSampleInfos.push_back(info);
+
+ buffer->release();
+ buffer = NULL;
+ }
+}
+
+int64_t MPEG4Writer::Track::getDuration() const {
+ return 10000; // XXX
+}
+
+void MPEG4Writer::Track::writeTrackHeader(int32_t trackID) {
+ const char *mime;
+ bool success = mMeta->findCString(kKeyMIMEType, &mime);
+ assert(success);
+
+ bool is_audio = !strncasecmp(mime, "audio/", 6);
+
+ time_t now = time(NULL);
+
+ mOwner->beginBox("trak");
+
+ mOwner->beginBox("tkhd");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(now); // creation time
+ mOwner->writeInt32(now); // modification time
+ mOwner->writeInt32(trackID);
+ mOwner->writeInt32(0); // reserved
+ mOwner->writeInt32(getDuration());
+ mOwner->writeInt32(0); // reserved
+ mOwner->writeInt32(0); // reserved
+ mOwner->writeInt16(0); // layer
+ mOwner->writeInt16(0); // alternate group
+ mOwner->writeInt16(is_audio ? 0x100 : 0); // volume
+ mOwner->writeInt16(0); // reserved
+
+ mOwner->writeInt32(0x10000); // matrix
+ mOwner->writeInt32(0);
+ mOwner->writeInt32(0);
+ mOwner->writeInt32(0);
+ mOwner->writeInt32(0x10000);
+ mOwner->writeInt32(0);
+ mOwner->writeInt32(0);
+ mOwner->writeInt32(0);
+ mOwner->writeInt32(0x40000000);
+
+ if (is_audio) {
+ mOwner->writeInt32(0);
+ mOwner->writeInt32(0);
+ } else {
+ int32_t width, height;
+ bool success = mMeta->findInt32(kKeyWidth, &width);
+ success = success && mMeta->findInt32(kKeyHeight, &height);
+ assert(success);
+
+ mOwner->writeInt32(width);
+ mOwner->writeInt32(height);
+ }
+ mOwner->endBox(); // tkhd
+
+ mOwner->beginBox("mdia");
+
+ mOwner->beginBox("mdhd");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(now); // creation time
+ mOwner->writeInt32(now); // modification time
+ mOwner->writeInt32(1000); // timescale
+ mOwner->writeInt32(getDuration());
+ mOwner->writeInt16(0); // language code XXX
+ mOwner->writeInt16(0); // predefined
+ mOwner->endBox();
+
+ mOwner->beginBox("hdlr");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(0); // predefined
+ mOwner->writeFourcc(is_audio ? "soun" : "vide");
+ mOwner->writeInt32(0); // reserved
+ mOwner->writeInt32(0); // reserved
+ mOwner->writeInt32(0); // reserved
+ mOwner->writeCString(""); // name
+ mOwner->endBox();
+
+ mOwner->beginBox("minf");
+
+ mOwner->beginBox("dinf");
+ mOwner->beginBox("dref");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(1);
+ mOwner->beginBox("url ");
+ mOwner->writeInt32(1); // version=0, flags=1
+ mOwner->endBox(); // url
+ mOwner->endBox(); // dref
+ mOwner->endBox(); // dinf
+
+ if (is_audio) {
+ mOwner->beginBox("smhd");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt16(0); // balance
+ mOwner->writeInt16(0); // reserved
+ mOwner->endBox();
+ } else {
+ mOwner->beginBox("vmhd");
+ mOwner->writeInt32(0x00000001); // version=0, flags=1
+ mOwner->writeInt16(0); // graphics mode
+ mOwner->writeInt16(0); // opcolor
+ mOwner->writeInt16(0);
+ mOwner->writeInt16(0);
+ mOwner->endBox();
+ }
+ mOwner->endBox(); // minf
+
+ mOwner->beginBox("stbl");
+
+ mOwner->beginBox("stsd");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(1); // entry count
+ if (is_audio) {
+ mOwner->beginBox("xxxx"); // audio format XXX
+ mOwner->writeInt32(0); // reserved
+ mOwner->writeInt16(0); // reserved
+ mOwner->writeInt16(0); // data ref index
+ mOwner->writeInt32(0); // reserved
+ mOwner->writeInt32(0); // reserved
+ mOwner->writeInt16(2); // channel count
+ mOwner->writeInt16(16); // sample size
+ mOwner->writeInt16(0); // predefined
+ mOwner->writeInt16(0); // reserved
+
+ int32_t samplerate;
+ bool success = mMeta->findInt32(kKeySampleRate, &samplerate);
+ assert(success);
+
+ mOwner->writeInt32(samplerate << 16);
+ mOwner->endBox();
+ } else {
+ if (!strcasecmp("video/mp4v-es", mime)) {
+ mOwner->beginBox("mp4v");
+ } else if (!strcasecmp("video/3gpp", mime)) {
+ mOwner->beginBox("s263");
+ } else {
+ assert(!"should not be here, unknown mime type.");
+ }
+
+ mOwner->writeInt32(0); // reserved
+ mOwner->writeInt16(0); // reserved
+ mOwner->writeInt16(0); // data ref index
+ mOwner->writeInt16(0); // predefined
+ mOwner->writeInt16(0); // reserved
+ mOwner->writeInt32(0); // predefined
+ mOwner->writeInt32(0); // predefined
+ mOwner->writeInt32(0); // predefined
+
+ int32_t width, height;
+ bool success = mMeta->findInt32(kKeyWidth, &width);
+ success = success && mMeta->findInt32(kKeyHeight, &height);
+ assert(success);
+
+ mOwner->writeInt16(width);
+ mOwner->writeInt16(height);
+ mOwner->writeInt32(0x480000); // horiz resolution
+ mOwner->writeInt32(0x480000); // vert resolution
+ mOwner->writeInt32(0); // reserved
+ mOwner->writeInt16(1); // frame count
+ mOwner->write(" ", 32);
+ mOwner->writeInt16(0x18); // depth
+ mOwner->writeInt16(-1); // predefined
+
+ assert(23 + mCodecSpecificDataSize < 128);
+
+ if (!strcasecmp("video/mp4v-es", mime)) {
+ mOwner->beginBox("esds");
+
+ mOwner->writeInt32(0); // version=0, flags=0
+
+ mOwner->writeInt8(0x03); // ES_DescrTag
+ mOwner->writeInt8(23 + mCodecSpecificDataSize);
+ mOwner->writeInt16(0x0000); // ES_ID
+ mOwner->writeInt8(0x1f);
+
+ mOwner->writeInt8(0x04); // DecoderConfigDescrTag
+ mOwner->writeInt8(15 + mCodecSpecificDataSize);
+ mOwner->writeInt8(0x20); // objectTypeIndication ISO/IEC 14492-2
+ mOwner->writeInt8(0x11); // streamType VisualStream
+
+ static const uint8_t kData[] = {
+ 0x01, 0x77, 0x00,
+ 0x00, 0x03, 0xe8, 0x00,
+ 0x00, 0x03, 0xe8, 0x00
+ };
+ mOwner->write(kData, sizeof(kData));
+
+ mOwner->writeInt8(0x05); // DecoderSpecificInfoTag
+
+ mOwner->writeInt8(mCodecSpecificDataSize);
+ mOwner->write(mCodecSpecificData, mCodecSpecificDataSize);
+
+ static const uint8_t kData2[] = {
+ 0x06, // SLConfigDescriptorTag
+ 0x01,
+ 0x02
+ };
+ mOwner->write(kData2, sizeof(kData2));
+
+ mOwner->endBox(); // esds
+ } else if (!strcasecmp("video/3gpp", mime)) {
+ mOwner->beginBox("d263");
+
+ mOwner->writeInt32(0); // vendor
+ mOwner->writeInt8(0); // decoder version
+ mOwner->writeInt8(10); // level: 10
+ mOwner->writeInt8(0); // profile: 0
+
+ mOwner->endBox(); // d263
+ }
+ mOwner->endBox(); // mp4v or s263
+ }
+ mOwner->endBox(); // stsd
+
+ mOwner->beginBox("stts");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(mSampleInfos.size() - 1);
+
+ List<SampleInfo>::iterator it = mSampleInfos.begin();
+ int64_t last = (*it).timestamp;
+ ++it;
+ while (it != mSampleInfos.end()) {
+ mOwner->writeInt32(1);
+ mOwner->writeInt32((*it).timestamp - last);
+
+ last = (*it).timestamp;
+
+ ++it;
+ }
+ mOwner->endBox(); // stts
+
+ mOwner->beginBox("stsz");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(0); // default sample size
+ mOwner->writeInt32(mSampleInfos.size());
+ for (List<SampleInfo>::iterator it = mSampleInfos.begin();
+ it != mSampleInfos.end(); ++it) {
+ mOwner->writeInt32((*it).size);
+ }
+ mOwner->endBox(); // stsz
+
+ mOwner->beginBox("stsc");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(mSampleInfos.size());
+ int32_t n = 1;
+ for (List<SampleInfo>::iterator it = mSampleInfos.begin();
+ it != mSampleInfos.end(); ++it, ++n) {
+ mOwner->writeInt32(n);
+ mOwner->writeInt32(1);
+ mOwner->writeInt32(1);
+ }
+ mOwner->endBox(); // stsc
+
+ mOwner->beginBox("co64");
+ mOwner->writeInt32(0); // version=0, flags=0
+ mOwner->writeInt32(mSampleInfos.size());
+ for (List<SampleInfo>::iterator it = mSampleInfos.begin();
+ it != mSampleInfos.end(); ++it, ++n) {
+ mOwner->writeInt64((*it).offset);
+ }
+ mOwner->endBox(); // co64
+
+ mOwner->endBox(); // stbl
+ mOwner->endBox(); // mdia
+ mOwner->endBox(); // trak
+}
+
+} // namespace android
diff --git a/media/libstagefright/MediaBuffer.cpp b/media/libstagefright/MediaBuffer.cpp
new file mode 100644
index 0000000..cd78dbd
--- /dev/null
+++ b/media/libstagefright/MediaBuffer.cpp
@@ -0,0 +1,174 @@
+/*
+ * Copyright (C) 2009 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_TAG "MediaBuffer"
+#include <utils/Log.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <errno.h>
+#include <pthread.h>
+#include <stdlib.h>
+
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+// XXX make this truly atomic.
+static int atomic_add(int *value, int delta) {
+ int prev_value = *value;
+ *value += delta;
+
+ return prev_value;
+}
+
+MediaBuffer::MediaBuffer(void *data, size_t size)
+ : mObserver(NULL),
+ mNextBuffer(NULL),
+ mRefCount(0),
+ mData(data),
+ mSize(size),
+ mRangeOffset(0),
+ mRangeLength(size),
+ mOwnsData(false),
+ mMetaData(new MetaData),
+ mOriginal(NULL) {
+}
+
+MediaBuffer::MediaBuffer(size_t size)
+ : mObserver(NULL),
+ mNextBuffer(NULL),
+ mRefCount(0),
+ mData(malloc(size)),
+ mSize(size),
+ mRangeOffset(0),
+ mRangeLength(size),
+ mOwnsData(true),
+ mMetaData(new MetaData),
+ mOriginal(NULL) {
+}
+
+void MediaBuffer::release() {
+ if (mObserver == NULL) {
+ assert(mRefCount == 0);
+ delete this;
+ return;
+ }
+
+ int prevCount = atomic_add(&mRefCount, -1);
+ if (prevCount == 1) {
+ if (mObserver == NULL) {
+ delete this;
+ return;
+ }
+
+ mObserver->signalBufferReturned(this);
+ }
+ assert(prevCount > 0);
+}
+
+void MediaBuffer::claim() {
+ assert(mObserver != NULL);
+ assert(mRefCount == 1);
+
+ mRefCount = 0;
+}
+
+void MediaBuffer::add_ref() {
+ atomic_add(&mRefCount, 1);
+}
+
+void *MediaBuffer::data() const {
+ return mData;
+}
+
+size_t MediaBuffer::size() const {
+ return mSize;
+}
+
+size_t MediaBuffer::range_offset() const {
+ return mRangeOffset;
+}
+
+size_t MediaBuffer::range_length() const {
+ return mRangeLength;
+}
+
+void MediaBuffer::set_range(size_t offset, size_t length) {
+ if (offset < 0 || offset + length > mSize) {
+ LOGE("offset = %d, length = %d, mSize = %d", offset, length, mSize);
+ }
+ assert(offset >= 0 && offset + length <= mSize);
+
+ mRangeOffset = offset;
+ mRangeLength = length;
+}
+
+sp<MetaData> MediaBuffer::meta_data() {
+ return mMetaData;
+}
+
+void MediaBuffer::reset() {
+ mMetaData->clear();
+ set_range(0, mSize);
+}
+
+MediaBuffer::~MediaBuffer() {
+ assert(mObserver == NULL);
+
+ if (mOwnsData && mData != NULL) {
+ free(mData);
+ mData = NULL;
+ }
+
+ if (mOriginal != NULL) {
+ mOriginal->release();
+ mOriginal = NULL;
+ }
+}
+
+void MediaBuffer::setObserver(MediaBufferObserver *observer) {
+ assert(observer == NULL || mObserver == NULL);
+ mObserver = observer;
+}
+
+void MediaBuffer::setNextBuffer(MediaBuffer *buffer) {
+ mNextBuffer = buffer;
+}
+
+MediaBuffer *MediaBuffer::nextBuffer() {
+ return mNextBuffer;
+}
+
+int MediaBuffer::refcount() const {
+ return mRefCount;
+}
+
+MediaBuffer *MediaBuffer::clone() {
+ MediaBuffer *buffer = new MediaBuffer(mData, mSize);
+ buffer->set_range(mRangeOffset, mRangeLength);
+ buffer->mMetaData = new MetaData(*mMetaData.get());
+
+ add_ref();
+ buffer->mOriginal = this;
+
+ return buffer;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/MediaBufferGroup.cpp b/media/libstagefright/MediaBufferGroup.cpp
new file mode 100644
index 0000000..aec7722
--- /dev/null
+++ b/media/libstagefright/MediaBufferGroup.cpp
@@ -0,0 +1,88 @@
+/*
+ * Copyright (C) 2009 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_TAG "MediaBufferGroup"
+#include <utils/Log.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaBufferGroup.h>
+
+namespace android {
+
+MediaBufferGroup::MediaBufferGroup()
+ : mFirstBuffer(NULL),
+ mLastBuffer(NULL) {
+}
+
+MediaBufferGroup::~MediaBufferGroup() {
+ MediaBuffer *next;
+ for (MediaBuffer *buffer = mFirstBuffer; buffer != NULL;
+ buffer = next) {
+ next = buffer->nextBuffer();
+
+ assert(buffer->refcount() == 0);
+
+ buffer->setObserver(NULL);
+ buffer->release();
+ }
+}
+
+void MediaBufferGroup::add_buffer(MediaBuffer *buffer) {
+ Mutex::Autolock autoLock(mLock);
+
+ buffer->setObserver(this);
+
+ if (mLastBuffer) {
+ mLastBuffer->setNextBuffer(buffer);
+ } else {
+ mFirstBuffer = buffer;
+ }
+
+ mLastBuffer = buffer;
+}
+
+status_t MediaBufferGroup::acquire_buffer(MediaBuffer **out) {
+ Mutex::Autolock autoLock(mLock);
+
+ for (;;) {
+ for (MediaBuffer *buffer = mFirstBuffer;
+ buffer != NULL; buffer = buffer->nextBuffer()) {
+ if (buffer->refcount() == 0) {
+ buffer->add_ref();
+ buffer->reset();
+
+ *out = buffer;
+ goto exit;
+ }
+ }
+
+ // All buffers are in use. Block until one of them is returned to us.
+ mCondition.wait(mLock);
+ }
+
+exit:
+ return OK;
+}
+
+void MediaBufferGroup::signalBufferReturned(MediaBuffer *) {
+ Mutex::Autolock autoLock(mLock);
+ mCondition.signal();
+}
+
+} // namespace android
diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp
new file mode 100644
index 0000000..bc66794
--- /dev/null
+++ b/media/libstagefright/MediaExtractor.cpp
@@ -0,0 +1,54 @@
+/*
+ * Copyright (C) 2009 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 "MediaExtractor"
+#include <utils/Log.h>
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/MP3Extractor.h>
+#include <media/stagefright/MPEG4Extractor.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <utils/String8.h>
+
+namespace android {
+
+// static
+MediaExtractor *MediaExtractor::Create(DataSource *source, const char *mime) {
+ String8 tmp;
+ if (mime == NULL) {
+ float confidence;
+ if (!source->sniff(&tmp, &confidence)) {
+ LOGE("FAILED to autodetect media content.");
+
+ return NULL;
+ }
+
+ mime = tmp.string();
+ LOGI("Autodetected media content as '%s' with confidence %.2f",
+ mime, confidence);
+ }
+
+ if (!strcasecmp(mime, "video/mp4") || !strcasecmp(mime, "audio/mp4")) {
+ return new MPEG4Extractor(source);
+ } else if (!strcasecmp(mime, "audio/mpeg")) {
+ return new MP3Extractor(source);
+ }
+
+ return NULL;
+}
+
+} // namespace android
diff --git a/media/libstagefright/MediaPlayerImpl.cpp b/media/libstagefright/MediaPlayerImpl.cpp
new file mode 100644
index 0000000..78fcdee
--- /dev/null
+++ b/media/libstagefright/MediaPlayerImpl.cpp
@@ -0,0 +1,693 @@
+/*
+ * Copyright (C) 2009 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 "MediaPlayerImpl"
+#include "utils/Log.h"
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <OMX_Component.h>
+
+#include <unistd.h>
+
+#include <media/stagefright/AudioPlayer.h>
+#include <media/stagefright/CachingDataSource.h>
+// #include <media/stagefright/CameraSource.h>
+#include <media/stagefright/HTTPDataSource.h>
+#include <media/stagefright/HTTPStream.h>
+#include <media/stagefright/MediaExtractor.h>
+#include <media/stagefright/MediaPlayerImpl.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/MmapSource.h>
+#include <media/stagefright/OMXDecoder.h>
+#include <media/stagefright/QComHardwareRenderer.h>
+#include <media/stagefright/ShoutcastSource.h>
+#include <media/stagefright/SoftwareRenderer.h>
+#include <media/stagefright/SurfaceRenderer.h>
+#include <media/stagefright/TimeSource.h>
+#include <ui/PixelFormat.h>
+#include <ui/Surface.h>
+
+namespace android {
+
+MediaPlayerImpl::MediaPlayerImpl(const char *uri)
+ : mInitCheck(NO_INIT),
+ mExtractor(NULL),
+ mTimeSource(NULL),
+ mAudioSource(NULL),
+ mAudioDecoder(NULL),
+ mAudioPlayer(NULL),
+ mVideoSource(NULL),
+ mVideoDecoder(NULL),
+ mVideoWidth(0),
+ mVideoHeight(0),
+ mVideoPosition(0),
+ mDuration(0),
+ mPlaying(false),
+ mPaused(false),
+ mRenderer(NULL),
+ mSeeking(false),
+ mFrameSize(0),
+ mUseSoftwareColorConversion(false) {
+ LOGI("MediaPlayerImpl(%s)", uri);
+ DataSource::RegisterDefaultSniffers();
+
+ status_t err = mClient.connect();
+ if (err != OK) {
+ LOGE("Failed to connect to OMXClient.");
+ return;
+ }
+
+ if (!strncasecmp("shoutcast://", uri, 12)) {
+ setAudioSource(makeShoutcastSource(uri));
+#if 0
+ } else if (!strncasecmp("camera:", uri, 7)) {
+ mVideoWidth = 480;
+ mVideoHeight = 320;
+ mVideoDecoder = CameraSource::Create();
+#endif
+ } else {
+ DataSource *source = NULL;
+ if (!strncasecmp("file://", uri, 7)) {
+ source = new MmapSource(uri + 7);
+ } else if (!strncasecmp("http://", uri, 7)) {
+ source = new HTTPDataSource(uri);
+ source = new CachingDataSource(source, 64 * 1024, 10);
+ } else {
+ // Assume it's a filename.
+ source = new MmapSource(uri);
+ }
+
+ mExtractor = MediaExtractor::Create(source);
+
+ if (mExtractor == NULL) {
+ return;
+ }
+ }
+
+ init();
+
+ mInitCheck = OK;
+}
+
+MediaPlayerImpl::MediaPlayerImpl(int fd, int64_t offset, int64_t length)
+ : mInitCheck(NO_INIT),
+ mExtractor(NULL),
+ mTimeSource(NULL),
+ mAudioSource(NULL),
+ mAudioDecoder(NULL),
+ mAudioPlayer(NULL),
+ mVideoSource(NULL),
+ mVideoDecoder(NULL),
+ mVideoWidth(0),
+ mVideoHeight(0),
+ mVideoPosition(0),
+ mDuration(0),
+ mPlaying(false),
+ mPaused(false),
+ mRenderer(NULL),
+ mSeeking(false),
+ mFrameSize(0),
+ mUseSoftwareColorConversion(false) {
+ LOGI("MediaPlayerImpl(%d, %lld, %lld)", fd, offset, length);
+ DataSource::RegisterDefaultSniffers();
+
+ status_t err = mClient.connect();
+ if (err != OK) {
+ LOGE("Failed to connect to OMXClient.");
+ return;
+ }
+
+ mExtractor = MediaExtractor::Create(
+ new MmapSource(fd, offset, length));
+
+ if (mExtractor == NULL) {
+ return;
+ }
+
+ init();
+
+ mInitCheck = OK;
+}
+
+status_t MediaPlayerImpl::initCheck() const {
+ return mInitCheck;
+}
+
+MediaPlayerImpl::~MediaPlayerImpl() {
+ stop();
+ setSurface(NULL);
+
+ LOGV("Shutting down audio.");
+ delete mAudioDecoder;
+ mAudioDecoder = NULL;
+
+ delete mAudioSource;
+ mAudioSource = NULL;
+
+ LOGV("Shutting down video.");
+ delete mVideoDecoder;
+ mVideoDecoder = NULL;
+
+ delete mVideoSource;
+ mVideoSource = NULL;
+
+ delete mExtractor;
+ mExtractor = NULL;
+
+ if (mInitCheck == OK) {
+ mClient.disconnect();
+ }
+
+ LOGV("~MediaPlayerImpl done.");
+}
+
+void MediaPlayerImpl::play() {
+ LOGI("play");
+
+ if (mPlaying) {
+ if (mPaused) {
+ if (mAudioSource != NULL) {
+ mAudioPlayer->resume();
+ }
+ mPaused = false;
+ }
+ return;
+ }
+
+ mPlaying = true;
+
+ if (mAudioSource != NULL) {
+ mAudioPlayer = new AudioPlayer(mAudioSink);
+ mAudioPlayer->setSource(mAudioDecoder);
+ mAudioPlayer->start();
+ mTimeSource = mAudioPlayer;
+ } else {
+ mTimeSource = new SystemTimeSource;
+ }
+
+ if (mVideoDecoder != NULL) {
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ pthread_create(&mVideoThread, &attr, VideoWrapper, this);
+
+ pthread_attr_destroy(&attr);
+ }
+}
+
+void MediaPlayerImpl::pause() {
+ if (!mPlaying || mPaused) {
+ return;
+ }
+
+ if (mAudioSource != NULL) {
+ mAudioPlayer->pause();
+ }
+
+ mPaused = true;
+}
+
+void MediaPlayerImpl::stop() {
+ if (!mPlaying) {
+ return;
+ }
+
+ mPlaying = false;
+
+ if (mVideoDecoder != NULL) {
+ void *dummy;
+ pthread_join(mVideoThread, &dummy);
+ }
+
+ if (mAudioSource != NULL) {
+ mAudioPlayer->stop();
+
+ delete mAudioPlayer;
+ mAudioPlayer = NULL;
+ } else {
+ delete mTimeSource;
+ }
+
+ mTimeSource = NULL;
+}
+
+// static
+void *MediaPlayerImpl::VideoWrapper(void *me) {
+ ((MediaPlayerImpl *)me)->videoEntry();
+
+ return NULL;
+}
+
+void MediaPlayerImpl::videoEntry() {
+ bool firstFrame = true;
+ bool eof = false;
+
+ status_t err = mVideoDecoder->start();
+ assert(err == OK);
+
+ while (mPlaying) {
+ MediaBuffer *buffer;
+
+ MediaSource::ReadOptions options;
+ bool seeking = false;
+
+ {
+ Mutex::Autolock autoLock(mLock);
+ if (mSeeking) {
+ LOGI("seek-options to %lld", mSeekTimeUs);
+ options.setSeekTo(mSeekTimeUs);
+
+ mSeeking = false;
+ seeking = true;
+ eof = false;
+ }
+ }
+
+ if (eof || mPaused) {
+ usleep(100000);
+ continue;
+ }
+
+ status_t err = mVideoDecoder->read(&buffer, &options);
+ assert((err == OK && buffer != NULL) || (err != OK && buffer == NULL));
+
+ if (err == ERROR_END_OF_STREAM || err != OK) {
+ eof = true;
+ continue;
+ }
+
+ if (buffer->range_length() == 0) {
+ // The final buffer is empty.
+ buffer->release();
+ continue;
+ }
+
+ int32_t units, scale;
+ bool success =
+ buffer->meta_data()->findInt32(kKeyTimeUnits, &units);
+ assert(success);
+ success =
+ buffer->meta_data()->findInt32(kKeyTimeScale, &scale);
+ assert(success);
+
+ int64_t pts_us = (int64_t)units * 1000000 / scale;
+ {
+ Mutex::Autolock autoLock(mLock);
+ mVideoPosition = pts_us;
+ }
+
+ if (seeking && mAudioPlayer != NULL) {
+ // Now that we know where exactly video seeked (taking sync-samples
+ // into account), we will seek the audio track to the same time.
+ mAudioPlayer->seekTo(pts_us);
+ }
+
+ if (firstFrame || seeking) {
+ mTimeSourceDeltaUs = mTimeSource->getRealTimeUs() - pts_us;
+ firstFrame = false;
+ }
+
+ displayOrDiscardFrame(buffer, pts_us);
+ }
+
+ mVideoDecoder->stop();
+}
+
+void MediaPlayerImpl::displayOrDiscardFrame(
+ MediaBuffer *buffer, int64_t pts_us) {
+ for (;;) {
+ if (!mPlaying || mPaused) {
+ buffer->release();
+ buffer = NULL;
+
+ return;
+ }
+
+ int64_t realtime_us, mediatime_us;
+ if (mAudioPlayer != NULL
+ && mAudioPlayer->getMediaTimeMapping(&realtime_us, &mediatime_us)) {
+ mTimeSourceDeltaUs = realtime_us - mediatime_us;
+ }
+
+ int64_t now_us = mTimeSource->getRealTimeUs();
+ now_us -= mTimeSourceDeltaUs;
+
+ int64_t delay_us = pts_us - now_us;
+
+ if (delay_us < -15000) {
+ // We're late.
+
+ LOGI("we're late by %lld ms, dropping a frame\n",
+ -delay_us / 1000);
+
+ buffer->release();
+ buffer = NULL;
+ return;
+ } else if (delay_us > 100000) {
+ LOGI("we're much too early (by %lld ms)\n",
+ delay_us / 1000);
+ usleep(100000);
+ continue;
+ } else if (delay_us > 0) {
+ usleep(delay_us);
+ }
+
+ break;
+ }
+
+ {
+ Mutex::Autolock autoLock(mLock);
+ if (mRenderer != NULL) {
+ sendFrameToISurface(buffer);
+ }
+ }
+
+ buffer->release();
+ buffer = NULL;
+}
+
+void MediaPlayerImpl::init() {
+ if (mExtractor != NULL) {
+ int num_tracks;
+ assert(mExtractor->countTracks(&num_tracks) == OK);
+
+ mDuration = 0;
+
+ for (int i = 0; i < num_tracks; ++i) {
+ const sp<MetaData> meta = mExtractor->getTrackMetaData(i);
+ assert(meta != NULL);
+
+ const char *mime;
+ if (!meta->findCString(kKeyMIMEType, &mime)) {
+ continue;
+ }
+
+ bool is_audio = false;
+ bool is_acceptable = false;
+ if (!strncasecmp(mime, "audio/", 6)) {
+ is_audio = true;
+ is_acceptable = (mAudioSource == NULL);
+ } else if (!strncasecmp(mime, "video/", 6)) {
+ is_acceptable = (mVideoSource == NULL);
+ }
+
+ if (!is_acceptable) {
+ continue;
+ }
+
+ MediaSource *source;
+ if (mExtractor->getTrack(i, &source) != OK) {
+ continue;
+ }
+
+ int32_t units, scale;
+ if (meta->findInt32(kKeyDuration, &units)
+ && meta->findInt32(kKeyTimeScale, &scale)) {
+ int64_t duration_us = (int64_t)units * 1000000 / scale;
+ if (duration_us > mDuration) {
+ mDuration = duration_us;
+ }
+ }
+
+ if (is_audio) {
+ setAudioSource(source);
+ } else {
+ setVideoSource(source);
+ }
+ }
+ }
+}
+
+void MediaPlayerImpl::setAudioSource(MediaSource *source) {
+ mAudioSource = source;
+
+ sp<MetaData> meta = source->getFormat();
+
+ mAudioDecoder = OMXDecoder::Create(&mClient, meta);
+ mAudioDecoder->setSource(source);
+}
+
+void MediaPlayerImpl::setVideoSource(MediaSource *source) {
+ LOGI("setVideoSource");
+ mVideoSource = source;
+
+ sp<MetaData> meta = source->getFormat();
+
+ bool success = meta->findInt32(kKeyWidth, &mVideoWidth);
+ assert(success);
+
+ success = meta->findInt32(kKeyHeight, &mVideoHeight);
+ assert(success);
+
+ mVideoDecoder = OMXDecoder::Create(&mClient, meta);
+ ((OMXDecoder *)mVideoDecoder)->setSource(source);
+
+ if (mISurface.get() != NULL || mSurface.get() != NULL) {
+ depopulateISurface();
+ populateISurface();
+ }
+}
+
+void MediaPlayerImpl::setSurface(const sp<Surface> &surface) {
+ LOGI("setSurface %p", surface.get());
+ Mutex::Autolock autoLock(mLock);
+
+ depopulateISurface();
+
+ mSurface = surface;
+ mISurface = NULL;
+
+ if (mSurface.get() != NULL) {
+ populateISurface();
+ }
+}
+
+void MediaPlayerImpl::setISurface(const sp<ISurface> &isurface) {
+ LOGI("setISurface %p", isurface.get());
+ Mutex::Autolock autoLock(mLock);
+
+ depopulateISurface();
+
+ mSurface = NULL;
+ mISurface = isurface;
+
+ if (mISurface.get() != NULL) {
+ populateISurface();
+ }
+}
+
+MediaSource *MediaPlayerImpl::makeShoutcastSource(const char *uri) {
+ if (strncasecmp(uri, "shoutcast://", 12)) {
+ return NULL;
+ }
+
+ string host;
+ string path;
+ int port;
+
+ char *slash = strchr(uri + 12, '/');
+ if (slash == NULL) {
+ host = uri + 12;
+ path = "/";
+ } else {
+ host = string(uri + 12, slash - (uri + 12));
+ path = slash;
+ }
+
+ char *colon = strchr(host.c_str(), ':');
+ if (colon == NULL) {
+ port = 80;
+ } else {
+ char *end;
+ long tmp = strtol(colon + 1, &end, 10);
+ assert(end > colon + 1);
+ assert(tmp > 0 && tmp < 65536);
+ port = tmp;
+
+ host = string(host, 0, colon - host.c_str());
+ }
+
+ LOGI("Connecting to host '%s', port %d, path '%s'",
+ host.c_str(), port, path.c_str());
+
+ HTTPStream *http = new HTTPStream;
+ int http_status;
+
+ for (;;) {
+ status_t err = http->connect(host.c_str(), port);
+ assert(err == OK);
+
+ err = http->send("GET ");
+ err = http->send(path.c_str());
+ err = http->send(" HTTP/1.1\r\n");
+ err = http->send("Host: ");
+ err = http->send(host.c_str());
+ err = http->send("\r\n");
+ err = http->send("Icy-MetaData: 1\r\n\r\n");
+
+ assert(OK == http->receive_header(&http_status));
+
+ if (http_status == 301 || http_status == 302) {
+ string location;
+ assert(http->find_header_value("Location", &location));
+
+ assert(string(location, 0, 7) == "http://");
+ location.erase(0, 7);
+ string::size_type slashPos = location.find('/');
+ if (slashPos == string::npos) {
+ slashPos = location.size();
+ location += '/';
+ }
+
+ http->disconnect();
+
+ LOGI("Redirecting to %s\n", location.c_str());
+
+ host = string(location, 0, slashPos);
+
+ string::size_type colonPos = host.find(':');
+ if (colonPos != string::npos) {
+ const char *start = host.c_str() + colonPos + 1;
+ char *end;
+ long tmp = strtol(start, &end, 10);
+ assert(end > start && *end == '\0');
+
+ port = (tmp >= 0 && tmp < 65536) ? (int)tmp : 80;
+ } else {
+ port = 80;
+ }
+
+ path = string(location, slashPos);
+
+ continue;
+ }
+
+ break;
+ }
+
+ if (http_status != 200) {
+ LOGE("Connection failed: http_status = %d", http_status);
+ return NULL;
+ }
+
+ MediaSource *source = new ShoutcastSource(http);
+
+ return source;
+}
+
+bool MediaPlayerImpl::isPlaying() const {
+ return mPlaying && !mPaused;
+}
+
+int64_t MediaPlayerImpl::getDuration() {
+ return mDuration;
+}
+
+int64_t MediaPlayerImpl::getPosition() {
+ int64_t position = 0;
+ if (mVideoSource != NULL) {
+ Mutex::Autolock autoLock(mLock);
+ position = mVideoPosition;
+ } else if (mAudioPlayer != NULL) {
+ position = mAudioPlayer->getMediaTimeUs();
+ }
+
+ return position;
+}
+
+status_t MediaPlayerImpl::seekTo(int64_t time) {
+ LOGI("seekTo %lld", time);
+
+ if (mPaused) {
+ return UNKNOWN_ERROR;
+ }
+
+ if (mVideoSource == NULL && mAudioPlayer != NULL) {
+ mAudioPlayer->seekTo(time);
+ } else {
+ Mutex::Autolock autoLock(mLock);
+ mSeekTimeUs = time;
+ mSeeking = true;
+ }
+
+ return OK;
+}
+
+void MediaPlayerImpl::populateISurface() {
+ if (mVideoSource == NULL) {
+ return;
+ }
+
+ sp<MetaData> meta = mVideoDecoder->getFormat();
+
+ int32_t format;
+ const char *component;
+ int32_t decodedWidth, decodedHeight;
+ bool success = meta->findInt32(kKeyColorFormat, &format);
+ success = success && meta->findCString(kKeyDecoderComponent, &component);
+ success = success && meta->findInt32(kKeyWidth, &decodedWidth);
+ success = success && meta->findInt32(kKeyHeight, &decodedHeight);
+ assert(success);
+
+ if (mSurface.get() != NULL) {
+ mRenderer =
+ new SurfaceRenderer(
+ mSurface, mVideoWidth, mVideoHeight,
+ decodedWidth, decodedHeight);
+ } else if (format == OMX_COLOR_FormatYUV420Planar
+ && !strncasecmp(component, "OMX.qcom.video.decoder.", 23)) {
+ mRenderer =
+ new QComHardwareRenderer(
+ mISurface, mVideoWidth, mVideoHeight,
+ decodedWidth, decodedHeight);
+ } else {
+ LOGW("Using software renderer.");
+ mRenderer = new SoftwareRenderer(
+ mISurface, mVideoWidth, mVideoHeight,
+ decodedWidth, decodedHeight);
+ }
+}
+
+void MediaPlayerImpl::depopulateISurface() {
+ delete mRenderer;
+ mRenderer = NULL;
+}
+
+void MediaPlayerImpl::sendFrameToISurface(MediaBuffer *buffer) {
+ void *platformPrivate;
+ if (!buffer->meta_data()->findPointer(
+ kKeyPlatformPrivate, &platformPrivate)) {
+ platformPrivate = NULL;
+ }
+
+ mRenderer->render(
+ (const uint8_t *)buffer->data() + buffer->range_offset(),
+ buffer->range_length(),
+ platformPrivate);
+}
+
+void MediaPlayerImpl::setAudioSink(
+ const sp<MediaPlayerBase::AudioSink> &audioSink) {
+ LOGI("setAudioSink %p", audioSink.get());
+ mAudioSink = audioSink;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/MediaSource.cpp b/media/libstagefright/MediaSource.cpp
new file mode 100644
index 0000000..ec89b74
--- /dev/null
+++ b/media/libstagefright/MediaSource.cpp
@@ -0,0 +1,60 @@
+/*
+ * Copyright (C) 2009 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 <media/stagefright/MediaSource.h>
+
+namespace android {
+
+MediaSource::MediaSource() {}
+
+MediaSource::~MediaSource() {}
+
+////////////////////////////////////////////////////////////////////////////////
+
+MediaSource::ReadOptions::ReadOptions() {
+ reset();
+}
+
+void MediaSource::ReadOptions::reset() {
+ mOptions = 0;
+ mSeekTimeUs = 0;
+ mLatenessUs = 0;
+}
+
+void MediaSource::ReadOptions::setSeekTo(int64_t time_us) {
+ mOptions |= kSeekTo_Option;
+ mSeekTimeUs = time_us;
+}
+
+void MediaSource::ReadOptions::clearSeekTo() {
+ mOptions &= ~kSeekTo_Option;
+ mSeekTimeUs = 0;
+}
+
+bool MediaSource::ReadOptions::getSeekTo(int64_t *time_us) const {
+ *time_us = mSeekTimeUs;
+ return (mOptions & kSeekTo_Option) != 0;
+}
+
+void MediaSource::ReadOptions::setLateBy(int64_t lateness_us) {
+ mLatenessUs = lateness_us;
+}
+
+int64_t MediaSource::ReadOptions::getLateBy() const {
+ return mLatenessUs;
+}
+
+} // namespace android
diff --git a/media/libstagefright/MetaData.cpp b/media/libstagefright/MetaData.cpp
new file mode 100644
index 0000000..5d23732
--- /dev/null
+++ b/media/libstagefright/MetaData.cpp
@@ -0,0 +1,232 @@
+/*
+ * Copyright (C) 2009 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 <assert.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <media/stagefright/MetaData.h>
+
+namespace android {
+
+MetaData::MetaData() {
+}
+
+MetaData::MetaData(const MetaData &from)
+ : RefBase(),
+ mItems(from.mItems) {
+}
+
+MetaData::~MetaData() {
+ clear();
+}
+
+void MetaData::clear() {
+ mItems.clear();
+}
+
+bool MetaData::remove(uint32_t key) {
+ ssize_t i = mItems.indexOfKey(key);
+
+ if (i < 0) {
+ return false;
+ }
+
+ mItems.removeItemsAt(i);
+
+ return true;
+}
+
+bool MetaData::setCString(uint32_t key, const char *value) {
+ return setData(key, TYPE_C_STRING, value, strlen(value) + 1);
+}
+
+bool MetaData::setInt32(uint32_t key, int32_t value) {
+ return setData(key, TYPE_INT32, &value, sizeof(value));
+}
+
+bool MetaData::setFloat(uint32_t key, float value) {
+ return setData(key, TYPE_FLOAT, &value, sizeof(value));
+}
+
+bool MetaData::setPointer(uint32_t key, void *value) {
+ return setData(key, TYPE_POINTER, &value, sizeof(value));
+}
+
+bool MetaData::findCString(uint32_t key, const char **value) {
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (!findData(key, &type, &data, &size) || type != TYPE_C_STRING) {
+ return false;
+ }
+
+ *value = (const char *)data;
+
+ return true;
+}
+
+bool MetaData::findInt32(uint32_t key, int32_t *value) {
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (!findData(key, &type, &data, &size) || type != TYPE_INT32) {
+ return false;
+ }
+
+ assert(size == sizeof(*value));
+
+ *value = *(int32_t *)data;
+
+ return true;
+}
+
+bool MetaData::findFloat(uint32_t key, float *value) {
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (!findData(key, &type, &data, &size) || type != TYPE_FLOAT) {
+ return false;
+ }
+
+ assert(size == sizeof(*value));
+
+ *value = *(float *)data;
+
+ return true;
+}
+
+bool MetaData::findPointer(uint32_t key, void **value) {
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (!findData(key, &type, &data, &size) || type != TYPE_POINTER) {
+ return false;
+ }
+
+ assert(size == sizeof(*value));
+
+ *value = *(void **)data;
+
+ return true;
+}
+
+bool MetaData::setData(
+ uint32_t key, uint32_t type, const void *data, size_t size) {
+ bool overwrote_existing = true;
+
+ ssize_t i = mItems.indexOfKey(key);
+ if (i < 0) {
+ typed_data item;
+ i = mItems.add(key, item);
+
+ overwrote_existing = false;
+ }
+
+ typed_data &item = mItems.editValueAt(i);
+
+ item.setData(type, data, size);
+
+ return overwrote_existing;
+}
+
+bool MetaData::findData(uint32_t key, uint32_t *type,
+ const void **data, size_t *size) const {
+ ssize_t i = mItems.indexOfKey(key);
+
+ if (i < 0) {
+ return false;
+ }
+
+ const typed_data &item = mItems.valueAt(i);
+
+ item.getData(type, data, size);
+
+ return true;
+}
+
+MetaData::typed_data::typed_data()
+ : mType(0),
+ mSize(0) {
+}
+
+MetaData::typed_data::~typed_data() {
+ clear();
+}
+
+MetaData::typed_data::typed_data(const typed_data &from)
+ : mType(from.mType),
+ mSize(0) {
+ allocateStorage(from.mSize);
+ memcpy(storage(), from.storage(), mSize);
+}
+
+MetaData::typed_data &MetaData::typed_data::operator=(
+ const MetaData::typed_data &from) {
+ if (this != &from) {
+ clear();
+ mType = from.mType;
+ allocateStorage(from.mSize);
+ memcpy(storage(), from.storage(), mSize);
+ }
+
+ return *this;
+}
+
+void MetaData::typed_data::clear() {
+ freeStorage();
+
+ mType = 0;
+}
+
+void MetaData::typed_data::setData(
+ uint32_t type, const void *data, size_t size) {
+ clear();
+
+ mType = type;
+ allocateStorage(size);
+ memcpy(storage(), data, size);
+}
+
+void MetaData::typed_data::getData(
+ uint32_t *type, const void **data, size_t *size) const {
+ *type = mType;
+ *size = mSize;
+ *data = storage();
+}
+
+void MetaData::typed_data::allocateStorage(size_t size) {
+ mSize = size;
+
+ if (usesReservoir()) {
+ return;
+ }
+
+ u.ext_data = malloc(mSize);
+}
+
+void MetaData::typed_data::freeStorage() {
+ if (!usesReservoir()) {
+ if (u.ext_data) {
+ free(u.ext_data);
+ }
+ }
+
+ mSize = 0;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/MmapSource.cpp b/media/libstagefright/MmapSource.cpp
new file mode 100644
index 0000000..7cb861c
--- /dev/null
+++ b/media/libstagefright/MmapSource.cpp
@@ -0,0 +1,112 @@
+/*
+ * Copyright (C) 2009 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 "MmapSource"
+#include <utils/Log.h>
+
+#include <sys/mman.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <fcntl.h>
+#include <string.h>
+#include <unistd.h>
+
+#include <media/stagefright/MmapSource.h>
+
+namespace android {
+
+MmapSource::MmapSource(const char *filename)
+ : mFd(open(filename, O_RDONLY)),
+ mBase(NULL),
+ mSize(0) {
+ LOGV("MmapSource '%s'", filename);
+ assert(mFd >= 0);
+
+ off_t size = lseek(mFd, 0, SEEK_END);
+ mSize = (size_t)size;
+
+ mBase = mmap(0, mSize, PROT_READ, MAP_FILE | MAP_SHARED, mFd, 0);
+
+ if (mBase == (void *)-1) {
+ mBase = NULL;
+
+ close(mFd);
+ mFd = -1;
+ }
+}
+
+MmapSource::MmapSource(int fd, int64_t offset, int64_t length)
+ : mFd(fd),
+ mBase(NULL),
+ mSize(length) {
+ LOGV("MmapSource fd:%d offset:%lld length:%lld", fd, offset, length);
+ assert(fd >= 0);
+
+ mBase = mmap(0, mSize, PROT_READ, MAP_FILE | MAP_SHARED, mFd, offset);
+
+ if (mBase == (void *)-1) {
+ mBase = NULL;
+
+ close(mFd);
+ mFd = -1;
+ }
+
+}
+
+MmapSource::~MmapSource() {
+ if (mFd != -1) {
+ munmap(mBase, mSize);
+ mBase = NULL;
+ mSize = 0;
+
+ close(mFd);
+ mFd = -1;
+ }
+}
+
+status_t MmapSource::InitCheck() const {
+ return mFd == -1 ? NO_INIT : OK;
+}
+
+ssize_t MmapSource::read_at(off_t offset, void *data, size_t size) {
+ LOGV("read_at offset:%ld data:%p size:%d", offset, data, size);
+ assert(offset >= 0);
+
+ size_t avail = 0;
+ if (offset >= 0 && offset < (off_t)mSize) {
+ avail = mSize - offset;
+ }
+
+ if (size > avail) {
+ size = avail;
+ }
+
+ memcpy(data, (const uint8_t *)mBase + offset, size);
+
+ return (ssize_t)size;
+}
+
+status_t MmapSource::getSize(off_t *size) {
+ *size = mSize;
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/OMXClient.cpp b/media/libstagefright/OMXClient.cpp
new file mode 100644
index 0000000..1bc8a44
--- /dev/null
+++ b/media/libstagefright/OMXClient.cpp
@@ -0,0 +1,369 @@
+/*
+ * Copyright (C) 2009 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 "OMXClient"
+#include <utils/Log.h>
+
+#include <sys/socket.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <binder/IServiceManager.h>
+#include <media/IMediaPlayerService.h>
+#include <media/IOMX.h>
+#include <media/stagefright/OMXClient.h>
+
+namespace android {
+
+OMXClient::OMXClient()
+ : mSock(-1) {
+}
+
+OMXClient::~OMXClient() {
+ disconnect();
+}
+
+status_t OMXClient::connect() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mSock >= 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ sp<IServiceManager> sm = defaultServiceManager();
+ sp<IBinder> binder = sm->getService(String16("media.player"));
+ sp<IMediaPlayerService> service = interface_cast<IMediaPlayerService>(binder);
+
+ assert(service.get() != NULL);
+
+ mOMX = service->createOMX();
+ assert(mOMX.get() != NULL);
+
+#if IOMX_USES_SOCKETS
+ status_t result = mOMX->connect(&mSock);
+ if (result != OK) {
+ mSock = -1;
+
+ mOMX = NULL;
+ return result;
+ }
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ int err = pthread_create(&mThread, &attr, ThreadWrapper, this);
+ assert(err == 0);
+
+ pthread_attr_destroy(&attr);
+#else
+ mReflector = new OMXClientReflector(this);
+#endif
+
+ return OK;
+}
+
+void OMXClient::disconnect() {
+ {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mSock < 0) {
+ return;
+ }
+
+ assert(mObservers.isEmpty());
+ }
+
+#if IOMX_USES_SOCKETS
+ omx_message msg;
+ msg.type = omx_message::DISCONNECT;
+ ssize_t n = send(mSock, &msg, sizeof(msg), 0);
+ assert(n > 0 && static_cast<size_t>(n) == sizeof(msg));
+
+ void *dummy;
+ pthread_join(mThread, &dummy);
+#else
+ mReflector->reset();
+ mReflector.clear();
+#endif
+}
+
+#if IOMX_USES_SOCKETS
+// static
+void *OMXClient::ThreadWrapper(void *me) {
+ ((OMXClient *)me)->threadEntry();
+
+ return NULL;
+}
+
+void OMXClient::threadEntry() {
+ bool done = false;
+ while (!done) {
+ omx_message msg;
+ ssize_t n = recv(mSock, &msg, sizeof(msg), 0);
+
+ if (n <= 0) {
+ break;
+ }
+
+ done = onOMXMessage(msg);
+ }
+
+ Mutex::Autolock autoLock(mLock);
+ close(mSock);
+ mSock = -1;
+}
+#endif
+
+status_t OMXClient::fillBuffer(IOMX::node_id node, IOMX::buffer_id buffer) {
+#if !IOMX_USES_SOCKETS
+ mOMX->fill_buffer(node, buffer);
+#else
+ if (mSock < 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ omx_message msg;
+ msg.type = omx_message::FILL_BUFFER;
+ msg.u.buffer_data.node = node;
+ msg.u.buffer_data.buffer = buffer;
+
+ ssize_t n = send(mSock, &msg, sizeof(msg), 0);
+ assert(n > 0 && static_cast<size_t>(n) == sizeof(msg));
+#endif
+
+ return OK;
+}
+
+status_t OMXClient::emptyBuffer(
+ IOMX::node_id node, IOMX::buffer_id buffer,
+ OMX_U32 range_offset, OMX_U32 range_length,
+ OMX_U32 flags, OMX_TICKS timestamp) {
+#if !IOMX_USES_SOCKETS
+ mOMX->empty_buffer(
+ node, buffer, range_offset, range_length, flags, timestamp);
+#else
+ if (mSock < 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ // XXX I don't like all this copying...
+
+ omx_message msg;
+ msg.type = omx_message::EMPTY_BUFFER;
+ msg.u.extended_buffer_data.node = node;
+ msg.u.extended_buffer_data.buffer = buffer;
+ msg.u.extended_buffer_data.range_offset = range_offset;
+ msg.u.extended_buffer_data.range_length = range_length;
+ msg.u.extended_buffer_data.flags = flags;
+ msg.u.extended_buffer_data.timestamp = timestamp;
+
+ ssize_t n = send(mSock, &msg, sizeof(msg), 0);
+ assert(n > 0 && static_cast<size_t>(n) == sizeof(msg));
+#endif
+
+ return OK;
+}
+
+status_t OMXClient::send_command(
+ IOMX::node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) {
+#if !IOMX_USES_SOCKETS
+ return mOMX->send_command(node, cmd, param);
+#else
+ if (mSock < 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ omx_message msg;
+ msg.type = omx_message::SEND_COMMAND;
+ msg.u.send_command_data.node = node;
+ msg.u.send_command_data.cmd = cmd;
+ msg.u.send_command_data.param = param;
+
+ ssize_t n = send(mSock, &msg, sizeof(msg), 0);
+ assert(n > 0 && static_cast<size_t>(n) == sizeof(msg));
+#endif
+
+ return OK;
+}
+
+status_t OMXClient::registerObserver(
+ IOMX::node_id node, OMXObserver *observer) {
+ Mutex::Autolock autoLock(&mLock);
+
+ ssize_t index = mObservers.indexOfKey(node);
+ if (index >= 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ mObservers.add(node, observer);
+ observer->start();
+
+#if !IOMX_USES_SOCKETS
+ mOMX->observe_node(node, mReflector);
+#endif
+
+ return OK;
+}
+
+void OMXClient::unregisterObserver(IOMX::node_id node) {
+ Mutex::Autolock autoLock(mLock);
+
+ ssize_t index = mObservers.indexOfKey(node);
+ assert(index >= 0);
+
+ if (index < 0) {
+ return;
+ }
+
+ OMXObserver *observer = mObservers.valueAt(index);
+ observer->stop();
+ mObservers.removeItemsAt(index);
+}
+
+bool OMXClient::onOMXMessage(const omx_message &msg) {
+ bool done = false;
+
+ switch (msg.type) {
+ case omx_message::EVENT:
+ {
+ LOGV("OnEvent node:%p event:%d data1:%ld data2:%ld",
+ msg.u.event_data.node,
+ msg.u.event_data.event,
+ msg.u.event_data.data1,
+ msg.u.event_data.data2);
+
+ break;
+ }
+
+ case omx_message::FILL_BUFFER_DONE:
+ {
+ LOGV("FillBufferDone %p", msg.u.extended_buffer_data.buffer);
+ break;
+ }
+
+ case omx_message::EMPTY_BUFFER_DONE:
+ {
+ LOGV("EmptyBufferDone %p", msg.u.buffer_data.buffer);
+ break;
+ }
+
+#if IOMX_USES_SOCKETS
+ case omx_message::DISCONNECTED:
+ {
+ LOGV("Disconnected");
+ done = true;
+ break;
+ }
+#endif
+
+ default:
+ LOGE("received unknown omx_message type %d", msg.type);
+ break;
+ }
+
+ Mutex::Autolock autoLock(mLock);
+ ssize_t index = mObservers.indexOfKey(msg.u.buffer_data.node);
+
+ if (index >= 0) {
+ mObservers.editValueAt(index)->postMessage(msg);
+ }
+
+ return done;
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+OMXObserver::OMXObserver() {
+}
+
+OMXObserver::~OMXObserver() {
+}
+
+void OMXObserver::start() {
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ int err = pthread_create(&mThread, &attr, ThreadWrapper, this);
+ assert(err == 0);
+
+ pthread_attr_destroy(&attr);
+}
+
+void OMXObserver::stop() {
+ omx_message msg;
+ msg.type = omx_message::QUIT_OBSERVER;
+ postMessage(msg);
+
+ void *dummy;
+ pthread_join(mThread, &dummy);
+}
+
+void OMXObserver::postMessage(const omx_message &msg) {
+ Mutex::Autolock autoLock(mLock);
+ mQueue.push_back(msg);
+ mQueueNotEmpty.signal();
+}
+
+// static
+void *OMXObserver::ThreadWrapper(void *me) {
+ static_cast<OMXObserver *>(me)->threadEntry();
+
+ return NULL;
+}
+
+void OMXObserver::threadEntry() {
+ for (;;) {
+ omx_message msg;
+
+ {
+ Mutex::Autolock autoLock(mLock);
+ while (mQueue.empty()) {
+ mQueueNotEmpty.wait(mLock);
+ }
+
+ msg = *mQueue.begin();
+ mQueue.erase(mQueue.begin());
+ }
+
+ if (msg.type == omx_message::QUIT_OBSERVER) {
+ break;
+ }
+
+ onOMXMessage(msg);
+ }
+}
+
+////////////////////////////////////////////////////////////////////////////////
+
+OMXClientReflector::OMXClientReflector(OMXClient *client)
+ : mClient(client) {
+}
+
+void OMXClientReflector::on_message(const omx_message &msg) {
+ if (mClient != NULL) {
+ mClient->onOMXMessage(msg);
+ }
+}
+
+void OMXClientReflector::reset() {
+ mClient = NULL;
+}
+
+} // namespace android
diff --git a/media/libstagefright/OMXDecoder.cpp b/media/libstagefright/OMXDecoder.cpp
new file mode 100644
index 0000000..c059a9d
--- /dev/null
+++ b/media/libstagefright/OMXDecoder.cpp
@@ -0,0 +1,1329 @@
+/*
+ * Copyright (C) 2009 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 "OMXDecoder"
+#include <utils/Log.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <OMX_Component.h>
+
+#include <media/stagefright/ESDS.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/OMXDecoder.h>
+
+namespace android {
+
+class OMXMediaBuffer : public MediaBuffer {
+public:
+ OMXMediaBuffer(IOMX::buffer_id buffer_id, const sp<IMemory> &mem)
+ : MediaBuffer(mem->pointer(),
+ mem->size()),
+ mBufferID(buffer_id),
+ mMem(mem) {
+ }
+
+ IOMX::buffer_id buffer_id() const { return mBufferID; }
+
+private:
+ IOMX::buffer_id mBufferID;
+ sp<IMemory> mMem;
+
+ OMXMediaBuffer(const OMXMediaBuffer &);
+ OMXMediaBuffer &operator=(const OMXMediaBuffer &);
+};
+
+struct CodecInfo {
+ const char *mime;
+ const char *codec;
+};
+
+static const CodecInfo kDecoderInfo[] = {
+ { "audio/mpeg", "OMX.PV.mp3dec" },
+ { "audio/3gpp", "OMX.PV.amrdec" },
+ { "audio/mp4a-latm", "OMX.PV.aacdec" },
+ { "video/mp4v-es", "OMX.qcom.video.decoder.mpeg4" },
+ { "video/mp4v-es", "OMX.PV.mpeg4dec" },
+ { "video/3gpp", "OMX.qcom.video.decoder.h263" },
+ { "video/3gpp", "OMX.PV.h263dec" },
+ { "video/avc", "OMX.qcom.video.decoder.avc" },
+ { "video/avc", "OMX.PV.avcdec" },
+};
+
+static const CodecInfo kEncoderInfo[] = {
+ { "audio/3gpp", "OMX.PV.amrencnb" },
+ { "audio/mp4a-latm", "OMX.PV.aacenc" },
+ { "video/mp4v-es", "OMX.qcom.video.encoder.mpeg4" },
+ { "video/mp4v-es", "OMX.PV.mpeg4enc" },
+ { "video/3gpp", "OMX.qcom.video.encoder.h263" },
+ { "video/3gpp", "OMX.PV.h263enc" },
+ { "video/avc", "OMX.PV.avcenc" },
+};
+
+static const char *GetCodec(const CodecInfo *info, size_t numInfos,
+ const char *mime, int index) {
+ assert(index >= 0);
+ for(size_t i = 0; i < numInfos; ++i) {
+ if (!strcasecmp(mime, info[i].mime)) {
+ if (index == 0) {
+ return info[i].codec;
+ }
+
+ --index;
+ }
+ }
+
+ return NULL;
+}
+
+// static
+OMXDecoder *OMXDecoder::Create(OMXClient *client, const sp<MetaData> &meta) {
+ const char *mime;
+ bool success = meta->findCString(kKeyMIMEType, &mime);
+ assert(success);
+
+ sp<IOMX> omx = client->interface();
+
+ const char *codec = NULL;
+ IOMX::node_id node = 0;
+ for (int index = 0;; ++index) {
+ codec = GetCodec(
+ kDecoderInfo, sizeof(kDecoderInfo) / sizeof(kDecoderInfo[0]),
+ mime, index);
+
+ if (!codec) {
+ return NULL;
+ }
+
+ LOGI("Attempting to allocate OMX node '%s'", codec);
+
+ status_t err = omx->allocate_node(codec, &node);
+ if (err == OK) {
+ break;
+ }
+ }
+
+ OMXDecoder *decoder = new OMXDecoder(client, node, mime, codec);
+
+ uint32_t type;
+ const void *data;
+ size_t size;
+ if (meta->findData(kKeyESDS, &type, &data, &size)) {
+ ESDS esds((const char *)data, size);
+ assert(esds.InitCheck() == OK);
+
+ const void *codec_specific_data;
+ size_t codec_specific_data_size;
+ esds.getCodecSpecificInfo(
+ &codec_specific_data, &codec_specific_data_size);
+
+ printf("found codec specific data of size %d\n",
+ codec_specific_data_size);
+
+ decoder->addCodecSpecificData(
+ codec_specific_data, codec_specific_data_size);
+ } else if (meta->findData(kKeyAVCC, &type, &data, &size)) {
+ printf("found avcc of size %d\n", size);
+
+ const uint8_t *ptr = (const uint8_t *)data + 6;
+ size -= 6;
+ while (size >= 2) {
+ size_t length = ptr[0] << 8 | ptr[1];
+
+ ptr += 2;
+ size -= 2;
+
+ // printf("length = %d, size = %d\n", length, size);
+
+ assert(size >= length);
+
+ decoder->addCodecSpecificData(ptr, length);
+
+ ptr += length;
+ size -= length;
+
+ if (size <= 1) {
+ break;
+ }
+
+ ptr++; // XXX skip trailing 0x01 byte???
+ --size;
+ }
+ }
+
+ return decoder;
+}
+
+// static
+OMXDecoder *OMXDecoder::CreateEncoder(
+ OMXClient *client, const sp<MetaData> &meta) {
+ const char *mime;
+ bool success = meta->findCString(kKeyMIMEType, &mime);
+ assert(success);
+
+ sp<IOMX> omx = client->interface();
+
+ const char *codec = NULL;
+ IOMX::node_id node = 0;
+ for (int index = 0;; ++index) {
+ codec = GetCodec(
+ kEncoderInfo, sizeof(kEncoderInfo) / sizeof(kEncoderInfo[0]),
+ mime, index);
+
+ if (!codec) {
+ return NULL;
+ }
+
+ LOGI("Attempting to allocate OMX node '%s'", codec);
+
+ status_t err = omx->allocate_node(codec, &node);
+ if (err == OK) {
+ break;
+ }
+ }
+
+ OMXDecoder *encoder = new OMXDecoder(client, node, mime, codec);
+
+ return encoder;
+}
+
+OMXDecoder::OMXDecoder(OMXClient *client, IOMX::node_id node,
+ const char *mime, const char *codec)
+ : mClient(client),
+ mOMX(mClient->interface()),
+ mNode(node),
+ mComponentName(strdup(codec)),
+ mIsMP3(!strcasecmp(mime, "audio/mpeg")),
+ mSource(NULL),
+ mCodecSpecificDataIterator(mCodecSpecificData.begin()),
+ mState(OMX_StateLoaded),
+ mPortStatusMask(kPortStatusActive << 2 | kPortStatusActive),
+ mShutdownInitiated(false),
+ mDealer(new MemoryDealer(3 * 1024 * 1024)),
+ mSeeking(false),
+ mStarted(false),
+ mErrorCondition(OK),
+ mReachedEndOfInput(false) {
+ mClient->registerObserver(mNode, this);
+
+ mBuffers.push(); // input buffers
+ mBuffers.push(); // output buffers
+}
+
+OMXDecoder::~OMXDecoder() {
+ if (mStarted) {
+ stop();
+ }
+
+ for (List<CodecSpecificData>::iterator it = mCodecSpecificData.begin();
+ it != mCodecSpecificData.end(); ++it) {
+ free((*it).data);
+ }
+ mCodecSpecificData.clear();
+
+ mClient->unregisterObserver(mNode);
+
+ status_t err = mOMX->free_node(mNode);
+ assert(err == OK);
+ mNode = 0;
+
+ free(mComponentName);
+ mComponentName = NULL;
+}
+
+void OMXDecoder::setSource(MediaSource *source) {
+ Mutex::Autolock autoLock(mLock);
+
+ assert(mSource == NULL);
+
+ mSource = source;
+ setup();
+}
+
+status_t OMXDecoder::start(MetaData *) {
+ assert(!mStarted);
+
+ // mDealer->dump("Decoder Dealer");
+
+ sp<MetaData> params = new MetaData;
+ if (!strcmp(mComponentName, "OMX.qcom.video.decoder.avc")) {
+ params->setInt32(kKeyNeedsNALFraming, true);
+ }
+
+ status_t err = mSource->start(params.get());
+
+ if (err != OK) {
+ return err;
+ }
+
+ postStart();
+
+ mStarted = true;
+
+ return OK;
+}
+
+status_t OMXDecoder::stop() {
+ assert(mStarted);
+
+ LOGI("Initiating OMX Node shutdown, busy polling.");
+ initiateShutdown();
+
+ // Important: initiateShutdown must be called first, _then_ release
+ // buffers we're holding onto.
+ while (!mOutputBuffers.empty()) {
+ MediaBuffer *buffer = *mOutputBuffers.begin();
+ mOutputBuffers.erase(mOutputBuffers.begin());
+
+ LOGV("releasing buffer %p.", buffer->data());
+
+ buffer->release();
+ buffer = NULL;
+ }
+
+ int attempt = 1;
+ while (mState != OMX_StateLoaded && attempt < 10) {
+ usleep(100000);
+
+ ++attempt;
+ }
+
+ if (mState != OMX_StateLoaded) {
+ LOGE("!!! OMX Node '%s' did NOT shutdown cleanly !!!", mComponentName);
+ } else {
+ LOGI("OMX Node '%s' has shutdown cleanly.", mComponentName);
+ }
+
+ mSource->stop();
+
+ mCodecSpecificDataIterator = mCodecSpecificData.begin();
+ mShutdownInitiated = false;
+ mSeeking = false;
+ mStarted = false;
+ mErrorCondition = OK;
+ mReachedEndOfInput = false;
+
+ return OK;
+}
+
+sp<MetaData> OMXDecoder::getFormat() {
+ return mOutputFormat;
+}
+
+status_t OMXDecoder::read(
+ MediaBuffer **out, const ReadOptions *options) {
+ assert(mStarted);
+
+ *out = NULL;
+
+ Mutex::Autolock autoLock(mLock);
+
+ if (mErrorCondition != OK && mErrorCondition != ERROR_END_OF_STREAM) {
+ // Errors are sticky.
+ return mErrorCondition;
+ }
+
+ int64_t seekTimeUs;
+ if (options && options->getSeekTo(&seekTimeUs)) {
+ LOGI("[%s] seeking to %lld us", mComponentName, seekTimeUs);
+
+ mErrorCondition = OK;
+ mReachedEndOfInput = false;
+
+ setPortStatus(kPortIndexInput, kPortStatusFlushing);
+ setPortStatus(kPortIndexOutput, kPortStatusFlushing);
+
+ mSeeking = true;
+ mSeekTimeUs = seekTimeUs;
+
+ while (!mOutputBuffers.empty()) {
+ OMXMediaBuffer *buffer =
+ static_cast<OMXMediaBuffer *>(*mOutputBuffers.begin());
+
+ // We could have used buffer->release() instead, but we're
+ // holding the lock and signalBufferReturned attempts to acquire
+ // the lock.
+ buffer->claim();
+ mBuffers.editItemAt(
+ kPortIndexOutput).push_back(buffer->buffer_id());
+ buffer = NULL;
+
+ mOutputBuffers.erase(mOutputBuffers.begin());
+ }
+
+ status_t err = mOMX->send_command(mNode, OMX_CommandFlush, -1);
+ assert(err == OK);
+
+ // Once flushing is completed buffers will again be scheduled to be
+ // filled/emptied.
+ }
+
+ while (mErrorCondition == OK && mOutputBuffers.empty()) {
+ mOutputBufferAvailable.wait(mLock);
+ }
+
+ if (!mOutputBuffers.empty()) {
+ MediaBuffer *buffer = *mOutputBuffers.begin();
+ mOutputBuffers.erase(mOutputBuffers.begin());
+
+ *out = buffer;
+
+ return OK;
+ } else {
+ assert(mErrorCondition != OK);
+ return mErrorCondition;
+ }
+}
+
+void OMXDecoder::addCodecSpecificData(const void *data, size_t size) {
+ CodecSpecificData specific;
+ specific.data = malloc(size);
+ memcpy(specific.data, data, size);
+ specific.size = size;
+
+ mCodecSpecificData.push_back(specific);
+ mCodecSpecificDataIterator = mCodecSpecificData.begin();
+}
+
+void OMXDecoder::onOMXMessage(const omx_message &msg) {
+ Mutex::Autolock autoLock(mLock);
+
+ switch (msg.type) {
+ case omx_message::START:
+ {
+ onStart();
+ break;
+ }
+
+ case omx_message::EVENT:
+ {
+ onEvent(msg.u.event_data.event, msg.u.event_data.data1,
+ msg.u.event_data.data2);
+ break;
+ }
+
+ case omx_message::EMPTY_BUFFER_DONE:
+ {
+ onEmptyBufferDone(msg.u.buffer_data.buffer);
+ break;
+ }
+
+ case omx_message::FILL_BUFFER_DONE:
+ case omx_message::INITIAL_FILL_BUFFER:
+ {
+ onFillBufferDone(msg);
+ break;
+ }
+
+ default:
+ LOGE("received unknown omx_message type %d", msg.type);
+ break;
+ }
+}
+
+void OMXDecoder::setAMRFormat() {
+ OMX_AUDIO_PARAM_AMRTYPE def;
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = kPortIndexInput;
+
+ status_t err =
+ mOMX->get_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def));
+
+ assert(err == NO_ERROR);
+
+ def.eAMRFrameFormat = OMX_AUDIO_AMRFrameFormatFSF;
+ def.eAMRBandMode = OMX_AUDIO_AMRBandModeNB0;
+
+ err = mOMX->set_parameter(mNode, OMX_IndexParamAudioAmr, &def, sizeof(def));
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::setAACFormat() {
+ OMX_AUDIO_PARAM_AACPROFILETYPE def;
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = kPortIndexInput;
+
+ status_t err =
+ mOMX->get_parameter(mNode, OMX_IndexParamAudioAac, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ def.eAACStreamFormat = OMX_AUDIO_AACStreamFormatMP4ADTS;
+
+ err = mOMX->set_parameter(mNode, OMX_IndexParamAudioAac, &def, sizeof(def));
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::setVideoOutputFormat(OMX_U32 width, OMX_U32 height) {
+ LOGI("setVideoOutputFormat width=%ld, height=%ld", width, height);
+
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
+
+ bool is_encoder = strstr(mComponentName, ".encoder.") != NULL; // XXX
+
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = is_encoder ? kPortIndexOutput : kPortIndexInput;
+
+ status_t err = mOMX->get_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+
+ assert(err == NO_ERROR);
+
+#if 1
+ // XXX Need a (much) better heuristic to compute input buffer sizes.
+ const size_t X = 64 * 1024;
+ if (def.nBufferSize < X) {
+ def.nBufferSize = X;
+ }
+#endif
+
+ assert(def.eDomain == OMX_PortDomainVideo);
+
+ video_def->nFrameWidth = width;
+ video_def->nFrameHeight = height;
+ // video_def.eCompressionFormat = OMX_VIDEO_CodingAVC;
+ video_def->eColorFormat = OMX_COLOR_FormatUnused;
+
+ err = mOMX->set_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ ////////////////////////////////////////////////////////////////////////////
+
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = is_encoder ? kPortIndexInput : kPortIndexOutput;
+
+ err = mOMX->get_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ assert(def.eDomain == OMX_PortDomainVideo);
+
+ def.nBufferSize =
+ (((width + 15) & -16) * ((height + 15) & -16) * 3) / 2; // YUV420
+
+ video_def->nFrameWidth = width;
+ video_def->nFrameHeight = height;
+ video_def->nStride = width;
+ // video_def->nSliceHeight = height;
+ video_def->eCompressionFormat = OMX_VIDEO_CodingUnused;
+// video_def->eColorFormat = OMX_COLOR_FormatYUV420Planar;
+
+ err = mOMX->set_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::setup() {
+ const sp<MetaData> &meta = mSource->getFormat();
+
+ const char *mime;
+ bool success = meta->findCString(kKeyMIMEType, &mime);
+ assert(success);
+
+ if (!strcasecmp(mime, "audio/3gpp")) {
+ setAMRFormat();
+ } else if (!strcasecmp(mime, "audio/mp4a-latm")) {
+ setAACFormat();
+ } else if (!strncasecmp(mime, "video/", 6)) {
+ int32_t width, height;
+ bool success = meta->findInt32(kKeyWidth, &width);
+ success = success && meta->findInt32(kKeyHeight, &height);
+ assert(success);
+
+ setVideoOutputFormat(width, height);
+ }
+
+ // dumpPortDefinition(0);
+ // dumpPortDefinition(1);
+
+ mOutputFormat = new MetaData;
+ mOutputFormat->setCString(kKeyDecoderComponent, mComponentName);
+
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = kPortIndexOutput;
+
+ status_t err = mOMX->get_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ switch (def.eDomain) {
+ case OMX_PortDomainAudio:
+ {
+ OMX_AUDIO_PORTDEFINITIONTYPE *audio_def = &def.format.audio;
+
+ assert(audio_def->eEncoding == OMX_AUDIO_CodingPCM);
+
+ OMX_AUDIO_PARAM_PCMMODETYPE params;
+ params.nSize = sizeof(params);
+ params.nVersion.s.nVersionMajor = 1;
+ params.nVersion.s.nVersionMinor = 1;
+ params.nPortIndex = kPortIndexOutput;
+
+ err = mOMX->get_parameter(
+ mNode, OMX_IndexParamAudioPcm, &params, sizeof(params));
+ assert(err == OK);
+
+ assert(params.eNumData == OMX_NumericalDataSigned);
+ assert(params.nBitPerSample == 16);
+ assert(params.ePCMMode == OMX_AUDIO_PCMModeLinear);
+
+ int32_t numChannels, sampleRate;
+ meta->findInt32(kKeyChannelCount, &numChannels);
+ meta->findInt32(kKeySampleRate, &sampleRate);
+
+ mOutputFormat->setCString(kKeyMIMEType, "audio/raw");
+ mOutputFormat->setInt32(kKeyChannelCount, numChannels);
+ mOutputFormat->setInt32(kKeySampleRate, sampleRate);
+ break;
+ }
+
+ case OMX_PortDomainVideo:
+ {
+ OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
+
+ if (video_def->eCompressionFormat == OMX_VIDEO_CodingUnused) {
+ mOutputFormat->setCString(kKeyMIMEType, "video/raw");
+ } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingMPEG4) {
+ mOutputFormat->setCString(kKeyMIMEType, "video/mp4v-es");
+ } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingH263) {
+ mOutputFormat->setCString(kKeyMIMEType, "video/3gpp");
+ } else if (video_def->eCompressionFormat == OMX_VIDEO_CodingAVC) {
+ mOutputFormat->setCString(kKeyMIMEType, "video/avc");
+ } else {
+ assert(!"Unknown compression format.");
+ }
+
+ if (!strcmp(mComponentName, "OMX.PV.avcdec")) {
+ // This component appears to be lying to me.
+ mOutputFormat->setInt32(
+ kKeyWidth, (video_def->nFrameWidth + 15) & -16);
+ mOutputFormat->setInt32(
+ kKeyHeight, (video_def->nFrameHeight + 15) & -16);
+ } else {
+ mOutputFormat->setInt32(kKeyWidth, video_def->nFrameWidth);
+ mOutputFormat->setInt32(kKeyHeight, video_def->nFrameHeight);
+ }
+
+ mOutputFormat->setInt32(kKeyColorFormat, video_def->eColorFormat);
+ break;
+ }
+
+ default:
+ {
+ assert(!"should not be here, neither audio nor video.");
+ break;
+ }
+ }
+}
+
+void OMXDecoder::onStart() {
+ bool needs_qcom_hack =
+ !strncmp(mComponentName, "OMX.qcom.video.", 15);
+
+ if (!needs_qcom_hack) {
+ status_t err =
+ mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle);
+ assert(err == NO_ERROR);
+ }
+
+ allocateBuffers(kPortIndexInput);
+ allocateBuffers(kPortIndexOutput);
+
+ if (needs_qcom_hack) {
+ // XXX this should happen before AllocateBuffers, but qcom's
+ // h264 vdec disagrees.
+ status_t err =
+ mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle);
+ assert(err == NO_ERROR);
+ }
+}
+
+void OMXDecoder::allocateBuffers(OMX_U32 port_index) {
+ assert(mBuffers[port_index].empty());
+
+ OMX_U32 num_buffers;
+ OMX_U32 buffer_size;
+
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nVersion.s.nRevision = 0;
+ def.nVersion.s.nStep = 0;
+ def.nPortIndex = port_index;
+
+ status_t err = mOMX->get_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ num_buffers = def.nBufferCountActual;
+ buffer_size = def.nBufferSize;
+
+ LOGV("[%s] port %ld: allocating %ld buffers of size %ld each\n",
+ mComponentName, port_index, num_buffers, buffer_size);
+
+ for (OMX_U32 i = 0; i < num_buffers; ++i) {
+ sp<IMemory> mem = mDealer->allocate(buffer_size);
+ assert(mem.get() != NULL);
+
+ IOMX::buffer_id buffer;
+ status_t err;
+
+ if (port_index == kPortIndexInput
+ && !strncmp(mComponentName, "OMX.qcom.video.encoder.", 23)) {
+ // qcom's H.263 encoder appears to want to allocate its own input
+ // buffers.
+ err = mOMX->allocate_buffer_with_backup(mNode, port_index, mem, &buffer);
+ if (err != OK) {
+ LOGE("[%s] allocate_buffer_with_backup failed with error %d",
+ mComponentName, err);
+ }
+ } else if (port_index == kPortIndexOutput
+ && !strncmp(mComponentName, "OMX.qcom.video.decoder.", 23)) {
+#if 1
+ err = mOMX->allocate_buffer_with_backup(mNode, port_index, mem, &buffer);
+#else
+ // XXX This is fine as long as we are either running the player
+ // inside the media server process or we are using the
+ // QComHardwareRenderer to output the frames.
+ err = mOMX->allocate_buffer(mNode, port_index, buffer_size, &buffer);
+#endif
+ if (err != OK) {
+ LOGE("[%s] allocate_buffer_with_backup failed with error %d",
+ mComponentName, err);
+ }
+ } else {
+ err = mOMX->use_buffer(mNode, port_index, mem, &buffer);
+ if (err != OK) {
+ LOGE("[%s] use_buffer failed with error %d",
+ mComponentName, err);
+ }
+ }
+ assert(err == OK);
+
+ LOGV("allocated %s buffer %p.",
+ port_index == kPortIndexInput ? "INPUT" : "OUTPUT",
+ buffer);
+
+ mBuffers.editItemAt(port_index).push_back(buffer);
+ mBufferMap.add(buffer, mem);
+
+ if (port_index == kPortIndexOutput) {
+ OMXMediaBuffer *media_buffer = new OMXMediaBuffer(buffer, mem);
+ media_buffer->setObserver(this);
+
+ mMediaBufferMap.add(buffer, media_buffer);
+ }
+ }
+
+ LOGV("allocate %s buffers done.",
+ port_index == kPortIndexInput ? "INPUT" : "OUTPUT");
+}
+
+void OMXDecoder::onEvent(
+ OMX_EVENTTYPE event, OMX_U32 data1, OMX_U32 data2) {
+ LOGV("[%s] onEvent event=%d, data1=%ld, data2=%ld",
+ mComponentName, event, data1, data2);
+
+ switch (event) {
+ case OMX_EventCmdComplete: {
+ onEventCmdComplete(
+ static_cast<OMX_COMMANDTYPE>(data1), data2);
+
+ break;
+ }
+
+ case OMX_EventPortSettingsChanged: {
+ onEventPortSettingsChanged(data1);
+ break;
+ }
+
+ case OMX_EventBufferFlag: {
+ // initiateShutdown();
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void OMXDecoder::onEventCmdComplete(OMX_COMMANDTYPE type, OMX_U32 data) {
+ switch (type) {
+ case OMX_CommandStateSet: {
+ OMX_STATETYPE state = static_cast<OMX_STATETYPE>(data);
+ onStateChanged(state);
+ break;
+ }
+
+ case OMX_CommandPortDisable: {
+ OMX_U32 port_index = data;
+ assert(getPortStatus(port_index) == kPortStatusDisabled);
+
+ status_t err =
+ mOMX->send_command(mNode, OMX_CommandPortEnable, port_index);
+
+ allocateBuffers(port_index);
+
+ break;
+ }
+
+ case OMX_CommandPortEnable: {
+ OMX_U32 port_index = data;
+ assert(getPortStatus(port_index) ==kPortStatusDisabled);
+ setPortStatus(port_index, kPortStatusActive);
+
+ assert(port_index == kPortIndexOutput);
+
+ BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput);
+ while (!obuffers->empty()) {
+ IOMX::buffer_id buffer = *obuffers->begin();
+ obuffers->erase(obuffers->begin());
+
+ status_t err = mClient->fillBuffer(mNode, buffer);
+ assert(err == NO_ERROR);
+ }
+
+ break;
+ }
+
+ case OMX_CommandFlush: {
+ OMX_U32 port_index = data;
+ LOGV("Port %ld flush complete.", port_index);
+ assert(getPortStatus(port_index) == kPortStatusFlushing);
+
+ setPortStatus(port_index, kPortStatusActive);
+ BufferList *buffers = &mBuffers.editItemAt(port_index);
+ while (!buffers->empty()) {
+ IOMX::buffer_id buffer = *buffers->begin();
+ buffers->erase(buffers->begin());
+
+ if (port_index == kPortIndexInput) {
+ postEmptyBufferDone(buffer);
+ } else {
+ postInitialFillBuffer(buffer);
+ }
+ }
+ break;
+ }
+
+ default:
+ break;
+ }
+}
+
+void OMXDecoder::onEventPortSettingsChanged(OMX_U32 port_index) {
+ assert(getPortStatus(port_index) == kPortStatusActive);
+ setPortStatus(port_index, kPortStatusDisabled);
+
+ status_t err =
+ mOMX->send_command(mNode, OMX_CommandPortDisable, port_index);
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::onStateChanged(OMX_STATETYPE to) {
+ if (mState == OMX_StateLoaded) {
+ assert(to == OMX_StateIdle);
+
+ mState = to;
+
+ status_t err =
+ mOMX->send_command(mNode, OMX_CommandStateSet, OMX_StateExecuting);
+ assert(err == NO_ERROR);
+ } else if (mState == OMX_StateIdle) {
+ if (to == OMX_StateExecuting) {
+ mState = to;
+
+ BufferList *ibuffers = &mBuffers.editItemAt(kPortIndexInput);
+ while (!ibuffers->empty()) {
+ IOMX::buffer_id buffer = *ibuffers->begin();
+ ibuffers->erase(ibuffers->begin());
+
+ postEmptyBufferDone(buffer);
+ }
+
+ BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput);
+ while (!obuffers->empty()) {
+ IOMX::buffer_id buffer = *obuffers->begin();
+ obuffers->erase(obuffers->begin());
+
+ postInitialFillBuffer(buffer);
+ }
+ } else {
+ assert(to == OMX_StateLoaded);
+
+ mState = to;
+
+ setPortStatus(kPortIndexInput, kPortStatusActive);
+ setPortStatus(kPortIndexOutput, kPortStatusActive);
+ }
+ } else if (mState == OMX_StateExecuting) {
+ assert(to == OMX_StateIdle);
+
+ mState = to;
+
+ LOGV("Executing->Idle complete, initiating Idle->Loaded");
+ status_t err =
+ mClient->send_command(mNode, OMX_CommandStateSet, OMX_StateLoaded);
+ assert(err == NO_ERROR);
+
+ BufferList *ibuffers = &mBuffers.editItemAt(kPortIndexInput);
+ for (BufferList::iterator it = ibuffers->begin();
+ it != ibuffers->end(); ++it) {
+ freeInputBuffer(*it);
+ }
+ ibuffers->clear();
+
+ BufferList *obuffers = &mBuffers.editItemAt(kPortIndexOutput);
+ for (BufferList::iterator it = obuffers->begin();
+ it != obuffers->end(); ++it) {
+ freeOutputBuffer(*it);
+ }
+ obuffers->clear();
+ }
+}
+
+void OMXDecoder::initiateShutdown() {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mShutdownInitiated) {
+ return;
+ }
+
+ if (mState == OMX_StateLoaded) {
+ return;
+ }
+
+ assert(mState == OMX_StateExecuting);
+
+ mShutdownInitiated = true;
+
+ status_t err =
+ mClient->send_command(mNode, OMX_CommandStateSet, OMX_StateIdle);
+ assert(err == NO_ERROR);
+
+ setPortStatus(kPortIndexInput, kPortStatusShutdown);
+ setPortStatus(kPortIndexOutput, kPortStatusShutdown);
+}
+
+void OMXDecoder::setPortStatus(OMX_U32 port_index, PortStatus status) {
+ int shift = 2 * port_index;
+
+ mPortStatusMask &= ~(3 << shift);
+ mPortStatusMask |= status << shift;
+}
+
+OMXDecoder::PortStatus OMXDecoder::getPortStatus(
+ OMX_U32 port_index) const {
+ int shift = 2 * port_index;
+
+ return static_cast<PortStatus>((mPortStatusMask >> shift) & 3);
+}
+
+void OMXDecoder::onEmptyBufferDone(IOMX::buffer_id buffer) {
+ LOGV("[%s] onEmptyBufferDone (%p)", mComponentName, buffer);
+
+ status_t err;
+ switch (getPortStatus(kPortIndexInput)) {
+ case kPortStatusDisabled:
+ freeInputBuffer(buffer);
+ err = NO_ERROR;
+ break;
+
+ case kPortStatusShutdown:
+ LOGV("We're shutting down, enqueue INPUT buffer %p.", buffer);
+ mBuffers.editItemAt(kPortIndexInput).push_back(buffer);
+ err = NO_ERROR;
+ break;
+
+ case kPortStatusFlushing:
+ LOGV("We're currently flushing, enqueue INPUT buffer %p.", buffer);
+ mBuffers.editItemAt(kPortIndexInput).push_back(buffer);
+ err = NO_ERROR;
+ break;
+
+ default:
+ onRealEmptyBufferDone(buffer);
+ err = NO_ERROR;
+ break;
+ }
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::onFillBufferDone(const omx_message &msg) {
+ IOMX::buffer_id buffer = msg.u.extended_buffer_data.buffer;
+
+ LOGV("[%s] onFillBufferDone (%p)", mComponentName, buffer);
+
+ status_t err;
+ switch (getPortStatus(kPortIndexOutput)) {
+ case kPortStatusDisabled:
+ freeOutputBuffer(buffer);
+ err = NO_ERROR;
+ break;
+ case kPortStatusShutdown:
+ LOGV("We're shutting down, enqueue OUTPUT buffer %p.", buffer);
+ mBuffers.editItemAt(kPortIndexOutput).push_back(buffer);
+ err = NO_ERROR;
+ break;
+
+ case kPortStatusFlushing:
+ LOGV("We're currently flushing, enqueue OUTPUT buffer %p.", buffer);
+ mBuffers.editItemAt(kPortIndexOutput).push_back(buffer);
+ err = NO_ERROR;
+ break;
+
+ default:
+ {
+ if (msg.type == omx_message::INITIAL_FILL_BUFFER) {
+ err = mClient->fillBuffer(mNode, buffer);
+ } else {
+ LOGV("[%s] Filled OUTPUT buffer %p, flags=0x%08lx.",
+ mComponentName, buffer, msg.u.extended_buffer_data.flags);
+
+ onRealFillBufferDone(msg);
+ err = NO_ERROR;
+ }
+ break;
+ }
+ }
+ assert(err == NO_ERROR);
+}
+
+void OMXDecoder::onRealEmptyBufferDone(IOMX::buffer_id buffer) {
+ if (mReachedEndOfInput) {
+ // We already sent the EOS notification.
+
+ mBuffers.editItemAt(kPortIndexInput).push_back(buffer);
+ return;
+ }
+
+ const sp<IMemory> mem = mBufferMap.valueFor(buffer);
+ assert(mem.get() != NULL);
+
+ static const uint8_t kNALStartCode[4] = { 0x00, 0x00, 0x00, 0x01 };
+
+ if (mCodecSpecificDataIterator != mCodecSpecificData.end()) {
+ List<CodecSpecificData>::iterator it = mCodecSpecificDataIterator;
+
+ size_t range_length = 0;
+
+ if (!strcmp(mComponentName, "OMX.qcom.video.decoder.avc")) {
+ assert((*mCodecSpecificDataIterator).size + 4 <= mem->size());
+
+ memcpy(mem->pointer(), kNALStartCode, 4);
+
+ memcpy((uint8_t *)mem->pointer() + 4, (*it).data, (*it).size);
+ range_length = (*it).size + 4;
+ } else {
+ assert((*mCodecSpecificDataIterator).size <= mem->size());
+
+ memcpy((uint8_t *)mem->pointer(), (*it).data, (*it).size);
+ range_length = (*it).size;
+ }
+
+ ++mCodecSpecificDataIterator;
+
+ status_t err = mClient->emptyBuffer(
+ mNode, buffer, 0, range_length,
+ OMX_BUFFERFLAG_ENDOFFRAME | OMX_BUFFERFLAG_CODECCONFIG,
+ 0);
+
+ assert(err == NO_ERROR);
+
+ return;
+ }
+
+ LOGV("[%s] waiting for input data", mComponentName);
+
+ MediaBuffer *input_buffer;
+ for (;;) {
+ status_t err;
+
+ if (mSeeking) {
+ MediaSource::ReadOptions options;
+ options.setSeekTo(mSeekTimeUs);
+
+ mSeeking = false;
+
+ err = mSource->read(&input_buffer, &options);
+ } else {
+ err = mSource->read(&input_buffer);
+ }
+ assert((err == OK && input_buffer != NULL)
+ || (err != OK && input_buffer == NULL));
+
+ if (err == ERROR_END_OF_STREAM) {
+ LOGE("[%s] Reached end of stream.", mComponentName);
+ mReachedEndOfInput = true;
+ } else {
+ LOGV("[%s] got input data", mComponentName);
+ }
+
+ if (err != OK) {
+ status_t err2 = mClient->emptyBuffer(
+ mNode, buffer, 0, 0, OMX_BUFFERFLAG_EOS, 0);
+
+ assert(err2 == NO_ERROR);
+ return;
+ }
+
+ if (mSeeking) {
+ input_buffer->release();
+ input_buffer = NULL;
+
+ continue;
+ }
+
+ break;
+ }
+
+ const uint8_t *src_data =
+ (const uint8_t *)input_buffer->data() + input_buffer->range_offset();
+
+ size_t src_length = input_buffer->range_length();
+ if (src_length == 195840) {
+ // When feeding the output of the AVC decoder into the H263 encoder,
+ // buffer sizes mismatch if width % 16 != 0 || height % 16 != 0.
+ src_length = 194400; // XXX HACK
+ } else if (src_length == 115200) {
+ src_length = 114240; // XXX HACK
+ }
+
+ if (src_length > mem->size()) {
+ LOGE("src_length=%d > mem->size() = %d\n",
+ src_length, mem->size());
+ }
+
+ assert(src_length <= mem->size());
+ memcpy(mem->pointer(), src_data, src_length);
+
+ OMX_U32 flags = 0;
+ if (!mIsMP3) {
+ // Only mp3 audio data may be streamed, all other data is assumed
+ // to be fed into the decoder at frame boundaries.
+ flags |= OMX_BUFFERFLAG_ENDOFFRAME;
+ }
+
+ int32_t units, scale;
+ bool success =
+ input_buffer->meta_data()->findInt32(kKeyTimeUnits, &units);
+
+ success = success &&
+ input_buffer->meta_data()->findInt32(kKeyTimeScale, &scale);
+
+ OMX_TICKS timestamp = 0;
+
+ if (success) {
+ // XXX units should be microseconds but PV treats them as milliseconds.
+ timestamp = ((OMX_S64)units * 1000) / scale;
+ }
+
+ input_buffer->release();
+ input_buffer = NULL;
+
+ LOGV("[%s] Calling EmptyBuffer on buffer %p",
+ mComponentName, buffer);
+
+ status_t err2 = mClient->emptyBuffer(
+ mNode, buffer, 0, src_length, flags, timestamp);
+ assert(err2 == OK);
+}
+
+void OMXDecoder::onRealFillBufferDone(const omx_message &msg) {
+ OMXMediaBuffer *media_buffer =
+ mMediaBufferMap.valueFor(msg.u.extended_buffer_data.buffer);
+
+ media_buffer->set_range(
+ msg.u.extended_buffer_data.range_offset,
+ msg.u.extended_buffer_data.range_length);
+
+ media_buffer->add_ref();
+
+ media_buffer->meta_data()->clear();
+
+ media_buffer->meta_data()->setInt32(
+ kKeyTimeUnits, msg.u.extended_buffer_data.timestamp);
+ media_buffer->meta_data()->setInt32(kKeyTimeScale, 1000);
+
+ if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_SYNCFRAME) {
+ media_buffer->meta_data()->setInt32(kKeyIsSyncFrame, true);
+ }
+
+ media_buffer->meta_data()->setPointer(
+ kKeyPlatformPrivate,
+ msg.u.extended_buffer_data.platform_private);
+
+ if (msg.u.extended_buffer_data.flags & OMX_BUFFERFLAG_EOS) {
+ mErrorCondition = ERROR_END_OF_STREAM;
+ }
+
+ mOutputBuffers.push_back(media_buffer);
+ mOutputBufferAvailable.signal();
+}
+
+void OMXDecoder::signalBufferReturned(MediaBuffer *_buffer) {
+ Mutex::Autolock autoLock(mLock);
+
+ OMXMediaBuffer *media_buffer = static_cast<OMXMediaBuffer *>(_buffer);
+
+ IOMX::buffer_id buffer = media_buffer->buffer_id();
+
+ PortStatus outputStatus = getPortStatus(kPortIndexOutput);
+ if (outputStatus == kPortStatusShutdown
+ || outputStatus == kPortStatusFlushing) {
+ mBuffers.editItemAt(kPortIndexOutput).push_back(buffer);
+ } else {
+ LOGV("[%s] Calling FillBuffer on buffer %p.", mComponentName, buffer);
+
+ status_t err = mClient->fillBuffer(mNode, buffer);
+ assert(err == NO_ERROR);
+ }
+}
+
+void OMXDecoder::freeInputBuffer(IOMX::buffer_id buffer) {
+ LOGV("freeInputBuffer %p", buffer);
+
+ status_t err = mOMX->free_buffer(mNode, kPortIndexInput, buffer);
+ assert(err == NO_ERROR);
+ mBufferMap.removeItem(buffer);
+
+ LOGV("freeInputBuffer %p done", buffer);
+}
+
+void OMXDecoder::freeOutputBuffer(IOMX::buffer_id buffer) {
+ LOGV("freeOutputBuffer %p", buffer);
+
+ status_t err = mOMX->free_buffer(mNode, kPortIndexOutput, buffer);
+ assert(err == NO_ERROR);
+ mBufferMap.removeItem(buffer);
+
+ ssize_t index = mMediaBufferMap.indexOfKey(buffer);
+ assert(index >= 0);
+ MediaBuffer *mbuffer = mMediaBufferMap.editValueAt(index);
+ mMediaBufferMap.removeItemsAt(index);
+ mbuffer->setObserver(NULL);
+ mbuffer->release();
+ mbuffer = NULL;
+
+ LOGV("freeOutputBuffer %p done", buffer);
+}
+
+void OMXDecoder::dumpPortDefinition(OMX_U32 port_index) {
+ OMX_PARAM_PORTDEFINITIONTYPE def;
+ def.nSize = sizeof(def);
+ def.nVersion.s.nVersionMajor = 1;
+ def.nVersion.s.nVersionMinor = 1;
+ def.nPortIndex = port_index;
+
+ status_t err = mOMX->get_parameter(
+ mNode, OMX_IndexParamPortDefinition, &def, sizeof(def));
+ assert(err == NO_ERROR);
+
+ LOGI("DumpPortDefinition on port %ld", port_index);
+ LOGI("nBufferCountActual = %ld, nBufferCountMin = %ld, nBufferSize = %ld",
+ def.nBufferCountActual, def.nBufferCountMin, def.nBufferSize);
+ switch (def.eDomain) {
+ case OMX_PortDomainAudio:
+ {
+ LOGI("eDomain = AUDIO");
+
+ if (port_index == kPortIndexOutput) {
+ OMX_AUDIO_PORTDEFINITIONTYPE *audio_def = &def.format.audio;
+ assert(audio_def->eEncoding == OMX_AUDIO_CodingPCM);
+
+ OMX_AUDIO_PARAM_PCMMODETYPE params;
+ params.nSize = sizeof(params);
+ params.nVersion.s.nVersionMajor = 1;
+ params.nVersion.s.nVersionMinor = 1;
+ params.nPortIndex = port_index;
+
+ err = mOMX->get_parameter(
+ mNode, OMX_IndexParamAudioPcm, &params, sizeof(params));
+ assert(err == OK);
+
+ assert(params.nChannels == 1 || params.bInterleaved);
+ assert(params.eNumData == OMX_NumericalDataSigned);
+ assert(params.nBitPerSample == 16);
+ assert(params.ePCMMode == OMX_AUDIO_PCMModeLinear);
+
+ LOGI("nChannels = %ld, nSamplingRate = %ld",
+ params.nChannels, params.nSamplingRate);
+ }
+
+ break;
+ }
+
+ case OMX_PortDomainVideo:
+ {
+ LOGI("eDomain = VIDEO");
+
+ OMX_VIDEO_PORTDEFINITIONTYPE *video_def = &def.format.video;
+ LOGI("nFrameWidth = %ld, nFrameHeight = %ld, nStride = %ld, "
+ "nSliceHeight = %ld",
+ video_def->nFrameWidth, video_def->nFrameHeight,
+ video_def->nStride, video_def->nSliceHeight);
+ LOGI("nBitrate = %ld, xFrameRate = %.2f",
+ video_def->nBitrate, video_def->xFramerate / 65536.0f);
+ LOGI("eCompressionFormat = %d, eColorFormat = %d",
+ video_def->eCompressionFormat, video_def->eColorFormat);
+
+ break;
+ }
+
+ default:
+ LOGI("eDomain = UNKNOWN");
+ break;
+ }
+}
+
+void OMXDecoder::postStart() {
+ omx_message msg;
+ msg.type = omx_message::START;
+ postMessage(msg);
+}
+
+void OMXDecoder::postEmptyBufferDone(IOMX::buffer_id buffer) {
+ omx_message msg;
+ msg.type = omx_message::EMPTY_BUFFER_DONE;
+ msg.u.buffer_data.node = mNode;
+ msg.u.buffer_data.buffer = buffer;
+ postMessage(msg);
+}
+
+void OMXDecoder::postInitialFillBuffer(IOMX::buffer_id buffer) {
+ omx_message msg;
+ msg.type = omx_message::INITIAL_FILL_BUFFER;
+ msg.u.buffer_data.node = mNode;
+ msg.u.buffer_data.buffer = buffer;
+ postMessage(msg);
+}
+
+} // namespace android
diff --git a/media/libstagefright/QComHardwareRenderer.cpp b/media/libstagefright/QComHardwareRenderer.cpp
new file mode 100644
index 0000000..5a23792
--- /dev/null
+++ b/media/libstagefright/QComHardwareRenderer.cpp
@@ -0,0 +1,139 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <binder/MemoryHeapBase.h>
+#include <binder/MemoryHeapPmem.h>
+#include <media/stagefright/QComHardwareRenderer.h>
+#include <ui/ISurface.h>
+
+namespace android {
+
+////////////////////////////////////////////////////////////////////////////////
+
+typedef struct PLATFORM_PRIVATE_ENTRY
+{
+ /* Entry type */
+ uint32_t type;
+
+ /* Pointer to platform specific entry */
+ void *entry;
+
+} PLATFORM_PRIVATE_ENTRY;
+
+typedef struct PLATFORM_PRIVATE_LIST
+{
+ /* Number of entries */
+ uint32_t nEntries;
+
+ /* Pointer to array of platform specific entries *
+ * Contiguous block of PLATFORM_PRIVATE_ENTRY elements */
+ PLATFORM_PRIVATE_ENTRY *entryList;
+
+} PLATFORM_PRIVATE_LIST;
+
+// data structures for tunneling buffers
+typedef struct PLATFORM_PRIVATE_PMEM_INFO
+{
+ /* pmem file descriptor */
+ uint32_t pmem_fd;
+ uint32_t offset;
+
+} PLATFORM_PRIVATE_PMEM_INFO;
+
+#define PLATFORM_PRIVATE_PMEM 1
+
+QComHardwareRenderer::QComHardwareRenderer(
+ const sp<ISurface> &surface,
+ size_t displayWidth, size_t displayHeight,
+ size_t decodedWidth, size_t decodedHeight)
+ : mISurface(surface),
+ mDisplayWidth(displayWidth),
+ mDisplayHeight(displayHeight),
+ mDecodedWidth(decodedWidth),
+ mDecodedHeight(decodedHeight),
+ mFrameSize((mDecodedWidth * mDecodedHeight * 3) / 2) {
+ assert(mISurface.get() != NULL);
+ assert(mDecodedWidth > 0);
+ assert(mDecodedHeight > 0);
+}
+
+QComHardwareRenderer::~QComHardwareRenderer() {
+ mISurface->unregisterBuffers();
+}
+
+void QComHardwareRenderer::render(
+ const void *data, size_t size, void *platformPrivate) {
+ size_t offset;
+ if (!getOffset(platformPrivate, &offset)) {
+ return;
+ }
+
+ mISurface->postBuffer(offset);
+}
+
+bool QComHardwareRenderer::getOffset(void *platformPrivate, size_t *offset) {
+ *offset = 0;
+
+ PLATFORM_PRIVATE_LIST *list = (PLATFORM_PRIVATE_LIST *)platformPrivate;
+ for (uint32_t i = 0; i < list->nEntries; ++i) {
+ if (list->entryList[i].type != PLATFORM_PRIVATE_PMEM) {
+ continue;
+ }
+
+ PLATFORM_PRIVATE_PMEM_INFO *info =
+ (PLATFORM_PRIVATE_PMEM_INFO *)list->entryList[i].entry;
+
+ if (info != NULL) {
+ if (mMemoryHeap.get() == NULL) {
+ publishBuffers(info->pmem_fd);
+ }
+
+ if (mMemoryHeap.get() == NULL) {
+ return false;
+ }
+
+ *offset = info->offset;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
+void QComHardwareRenderer::publishBuffers(uint32_t pmem_fd) {
+ sp<MemoryHeapBase> master =
+ reinterpret_cast<MemoryHeapBase *>(pmem_fd);
+
+ master->setDevice("/dev/pmem");
+
+ mMemoryHeap = new MemoryHeapPmem(master, 0);
+ mMemoryHeap->slap();
+
+ ISurface::BufferHeap bufferHeap(
+ mDisplayWidth, mDisplayHeight,
+ mDecodedWidth, mDecodedHeight,
+ PIXEL_FORMAT_YCbCr_420_SP,
+ mMemoryHeap);
+
+ status_t err = mISurface->registerBuffers(bufferHeap);
+ assert(err == OK);
+}
+
+} // namespace android
diff --git a/media/libstagefright/SampleTable.cpp b/media/libstagefright/SampleTable.cpp
new file mode 100644
index 0000000..8f1fa67
--- /dev/null
+++ b/media/libstagefright/SampleTable.cpp
@@ -0,0 +1,598 @@
+/*
+ * Copyright (C) 2009 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_TAG "SampleTable"
+#include <utils/Log.h>
+
+#include <arpa/inet.h>
+#include <assert.h>
+
+#include <media/stagefright/DataSource.h>
+#include <media/stagefright/SampleTable.h>
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+static const uint32_t kChunkOffsetType32 = FOURCC('s', 't', 'c', 'o');
+static const uint32_t kChunkOffsetType64 = FOURCC('c', 'o', '6', '4');
+static const uint32_t kSampleSizeType32 = FOURCC('s', 't', 's', 'z');
+static const uint32_t kSampleSizeTypeCompact = FOURCC('s', 't', 'z', '2');
+
+SampleTable::SampleTable(DataSource *source)
+ : mDataSource(source),
+ mChunkOffsetOffset(-1),
+ mChunkOffsetType(0),
+ mNumChunkOffsets(0),
+ mSampleToChunkOffset(-1),
+ mNumSampleToChunkOffsets(0),
+ mSampleSizeOffset(-1),
+ mSampleSizeFieldSize(0),
+ mDefaultSampleSize(0),
+ mNumSampleSizes(0),
+ mTimeToSampleCount(0),
+ mTimeToSample(NULL),
+ mSyncSampleOffset(-1),
+ mNumSyncSamples(0) {
+}
+
+SampleTable::~SampleTable() {
+ delete[] mTimeToSample;
+ mTimeToSample = NULL;
+}
+
+status_t SampleTable::setChunkOffsetParams(
+ uint32_t type, off_t data_offset, off_t data_size) {
+ if (mChunkOffsetOffset >= 0) {
+ return ERROR_MALFORMED;
+ }
+
+ assert(type == kChunkOffsetType32 || type == kChunkOffsetType64);
+
+ mChunkOffsetOffset = data_offset;
+ mChunkOffsetType = type;
+
+ if (data_size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[8];
+ if (mDataSource->read_at(
+ data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(header) != 0) {
+ // Expected version = 0, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ mNumChunkOffsets = U32_AT(&header[4]);
+
+ if (mChunkOffsetType == kChunkOffsetType32) {
+ if (data_size < 8 + mNumChunkOffsets * 4) {
+ return ERROR_MALFORMED;
+ }
+ } else {
+ if (data_size < 8 + mNumChunkOffsets * 8) {
+ return ERROR_MALFORMED;
+ }
+ }
+
+ return OK;
+}
+
+status_t SampleTable::setSampleToChunkParams(
+ off_t data_offset, off_t data_size) {
+ if (mSampleToChunkOffset >= 0) {
+ return ERROR_MALFORMED;
+ }
+
+ mSampleToChunkOffset = data_offset;
+
+ if (data_size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[8];
+ if (mDataSource->read_at(
+ data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(header) != 0) {
+ // Expected version = 0, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ mNumSampleToChunkOffsets = U32_AT(&header[4]);
+
+ if (data_size < 8 + mNumSampleToChunkOffsets * 12) {
+ return ERROR_MALFORMED;
+ }
+
+ return OK;
+}
+
+status_t SampleTable::setSampleSizeParams(
+ uint32_t type, off_t data_offset, off_t data_size) {
+ if (mSampleSizeOffset >= 0) {
+ return ERROR_MALFORMED;
+ }
+
+ assert(type == kSampleSizeType32 || type == kSampleSizeTypeCompact);
+
+ mSampleSizeOffset = data_offset;
+
+ if (data_size < 12) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[12];
+ if (mDataSource->read_at(
+ data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(header) != 0) {
+ // Expected version = 0, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ mDefaultSampleSize = U32_AT(&header[4]);
+ mNumSampleSizes = U32_AT(&header[8]);
+
+ if (type == kSampleSizeType32) {
+ mSampleSizeFieldSize = 32;
+
+ if (mDefaultSampleSize != 0) {
+ return OK;
+ }
+
+ if (data_size < 12 + mNumSampleSizes * 4) {
+ return ERROR_MALFORMED;
+ }
+ } else {
+ if ((mDefaultSampleSize & 0xffffff00) != 0) {
+ // The high 24 bits are reserved and must be 0.
+ return ERROR_MALFORMED;
+ }
+
+ mSampleSizeFieldSize = mDefaultSampleSize & 0xf;
+ mDefaultSampleSize = 0;
+
+ if (mSampleSizeFieldSize != 4 && mSampleSizeFieldSize != 8
+ && mSampleSizeFieldSize != 16) {
+ return ERROR_MALFORMED;
+ }
+
+ if (data_size < 12 + (mNumSampleSizes * mSampleSizeFieldSize + 4) / 8) {
+ return ERROR_MALFORMED;
+ }
+ }
+
+ return OK;
+}
+
+status_t SampleTable::setTimeToSampleParams(
+ off_t data_offset, off_t data_size) {
+ if (mTimeToSample != NULL || data_size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ uint8_t header[8];
+ if (mDataSource->read_at(
+ data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(header) != 0) {
+ // Expected version = 0, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ mTimeToSampleCount = U32_AT(&header[4]);
+ mTimeToSample = new uint32_t[mTimeToSampleCount * 2];
+
+ size_t size = sizeof(uint32_t) * mTimeToSampleCount * 2;
+ if (mDataSource->read_at(
+ data_offset + 8, mTimeToSample, size) < (ssize_t)size) {
+ return ERROR_IO;
+ }
+
+ for (uint32_t i = 0; i < mTimeToSampleCount * 2; ++i) {
+ mTimeToSample[i] = ntohl(mTimeToSample[i]);
+ }
+
+ return OK;
+}
+
+status_t SampleTable::setSyncSampleParams(off_t data_offset, off_t data_size) {
+ if (mSyncSampleOffset >= 0 || data_size < 8) {
+ return ERROR_MALFORMED;
+ }
+
+ mSyncSampleOffset = data_offset;
+
+ uint8_t header[8];
+ if (mDataSource->read_at(
+ data_offset, header, sizeof(header)) < (ssize_t)sizeof(header)) {
+ return ERROR_IO;
+ }
+
+ if (U32_AT(header) != 0) {
+ // Expected version = 0, flags = 0.
+ return ERROR_MALFORMED;
+ }
+
+ mNumSyncSamples = U32_AT(&header[4]);
+
+ if (mNumSyncSamples < 2) {
+ LOGW("Table of sync samples is empty or has only a single entry!");
+ }
+ return OK;
+}
+
+uint32_t SampleTable::countChunkOffsets() const {
+ return mNumChunkOffsets;
+}
+
+status_t SampleTable::getChunkOffset(uint32_t chunk_index, off_t *offset) {
+ *offset = 0;
+
+ if (mChunkOffsetOffset < 0) {
+ return ERROR_MALFORMED;
+ }
+
+ if (chunk_index >= mNumChunkOffsets) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ if (mChunkOffsetType == kChunkOffsetType32) {
+ uint32_t offset32;
+
+ if (mDataSource->read_at(
+ mChunkOffsetOffset + 8 + 4 * chunk_index,
+ &offset32,
+ sizeof(offset32)) < (ssize_t)sizeof(offset32)) {
+ return ERROR_IO;
+ }
+
+ *offset = ntohl(offset32);
+ } else {
+ assert(mChunkOffsetOffset == kChunkOffsetType64);
+
+ uint64_t offset64;
+ if (mDataSource->read_at(
+ mChunkOffsetOffset + 8 + 8 * chunk_index,
+ &offset64,
+ sizeof(offset64)) < (ssize_t)sizeof(offset64)) {
+ return ERROR_IO;
+ }
+
+ *offset = ntoh64(offset64);
+ }
+
+ return OK;
+}
+
+status_t SampleTable::getChunkForSample(
+ uint32_t sample_index,
+ uint32_t *chunk_index,
+ uint32_t *chunk_relative_sample_index,
+ uint32_t *desc_index) {
+ *chunk_index = 0;
+ *chunk_relative_sample_index = 0;
+ *desc_index = 0;
+
+ if (mSampleToChunkOffset < 0) {
+ return ERROR_MALFORMED;
+ }
+
+ if (sample_index >= countSamples()) {
+ return ERROR_END_OF_STREAM;
+ }
+
+ uint32_t first_chunk = 0;
+ uint32_t samples_per_chunk = 0;
+ uint32_t chunk_desc_index = 0;
+
+ uint32_t index = 0;
+ while (index < mNumSampleToChunkOffsets) {
+ uint8_t buffer[12];
+ if (mDataSource->read_at(mSampleToChunkOffset + 8 + index * 12,
+ buffer, sizeof(buffer)) < (ssize_t)sizeof(buffer)) {
+ return ERROR_IO;
+ }
+
+ uint32_t stop_chunk = U32_AT(buffer);
+ if (sample_index < (stop_chunk - first_chunk) * samples_per_chunk) {
+ break;
+ }
+
+ sample_index -= (stop_chunk - first_chunk) * samples_per_chunk;
+ first_chunk = stop_chunk;
+ samples_per_chunk = U32_AT(&buffer[4]);
+ chunk_desc_index = U32_AT(&buffer[8]);
+
+ ++index;
+ }
+
+ *chunk_index = sample_index / samples_per_chunk + first_chunk - 1;
+ *chunk_relative_sample_index = sample_index % samples_per_chunk;
+ *desc_index = chunk_desc_index;
+
+ return OK;
+}
+
+uint32_t SampleTable::countSamples() const {
+ return mNumSampleSizes;
+}
+
+status_t SampleTable::getSampleSize(
+ uint32_t sample_index, size_t *sample_size) {
+ *sample_size = 0;
+
+ if (mSampleSizeOffset < 0) {
+ return ERROR_MALFORMED;
+ }
+
+ if (sample_index >= mNumSampleSizes) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ if (mDefaultSampleSize > 0) {
+ *sample_size = mDefaultSampleSize;
+ return OK;
+ }
+
+ switch (mSampleSizeFieldSize) {
+ case 32:
+ {
+ if (mDataSource->read_at(
+ mSampleSizeOffset + 12 + 4 * sample_index,
+ sample_size, sizeof(*sample_size)) < (ssize_t)sizeof(*sample_size)) {
+ return ERROR_IO;
+ }
+
+ *sample_size = ntohl(*sample_size);
+ break;
+ }
+
+ case 16:
+ {
+ uint16_t x;
+ if (mDataSource->read_at(
+ mSampleSizeOffset + 12 + 2 * sample_index,
+ &x, sizeof(x)) < (ssize_t)sizeof(x)) {
+ return ERROR_IO;
+ }
+
+ *sample_size = ntohs(x);
+ break;
+ }
+
+ case 8:
+ {
+ uint8_t x;
+ if (mDataSource->read_at(
+ mSampleSizeOffset + 12 + sample_index,
+ &x, sizeof(x)) < (ssize_t)sizeof(x)) {
+ return ERROR_IO;
+ }
+
+ *sample_size = x;
+ break;
+ }
+
+ default:
+ {
+ assert(mSampleSizeFieldSize == 4);
+
+ uint8_t x;
+ if (mDataSource->read_at(
+ mSampleSizeOffset + 12 + sample_index / 2,
+ &x, sizeof(x)) < (ssize_t)sizeof(x)) {
+ return ERROR_IO;
+ }
+
+ *sample_size = (sample_index & 1) ? x & 0x0f : x >> 4;
+ break;
+ }
+ }
+
+ return OK;
+}
+
+status_t SampleTable::getSampleOffsetAndSize(
+ uint32_t sample_index, off_t *offset, size_t *size) {
+ Mutex::Autolock autoLock(mLock);
+
+ *offset = 0;
+ *size = 0;
+
+ uint32_t chunk_index;
+ uint32_t chunk_relative_sample_index;
+ uint32_t desc_index;
+ status_t err = getChunkForSample(
+ sample_index, &chunk_index, &chunk_relative_sample_index,
+ &desc_index);
+
+ if (err != OK) {
+ return err;
+ }
+
+ err = getChunkOffset(chunk_index, offset);
+
+ if (err != OK) {
+ return err;
+ }
+
+ for (uint32_t j = 0; j < chunk_relative_sample_index; ++j) {
+ size_t sample_size;
+ err = getSampleSize(sample_index - j - 1, &sample_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ *offset += sample_size;
+ }
+
+ err = getSampleSize(sample_index, size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ return OK;
+}
+
+status_t SampleTable::getMaxSampleSize(size_t *max_size) {
+ Mutex::Autolock autoLock(mLock);
+
+ *max_size = 0;
+
+ for (uint32_t i = 0; i < mNumSampleSizes; ++i) {
+ size_t sample_size;
+ status_t err = getSampleSize(i, &sample_size);
+
+ if (err != OK) {
+ return err;
+ }
+
+ if (sample_size > *max_size) {
+ *max_size = sample_size;
+ }
+ }
+
+ return OK;
+}
+
+status_t SampleTable::getDecodingTime(uint32_t sample_index, uint32_t *time) {
+ // XXX FIXME idiotic (for the common use-case) O(n) algorithm below...
+
+ Mutex::Autolock autoLock(mLock);
+
+ if (sample_index >= mNumSampleSizes) {
+ return ERROR_OUT_OF_RANGE;
+ }
+
+ uint32_t cur_sample = 0;
+ *time = 0;
+ for (uint32_t i = 0; i < mTimeToSampleCount; ++i) {
+ uint32_t n = mTimeToSample[2 * i];
+ uint32_t delta = mTimeToSample[2 * i + 1];
+
+ if (sample_index < cur_sample + n) {
+ *time += delta * (sample_index - cur_sample);
+
+ return OK;
+ }
+
+ *time += delta * n;
+ cur_sample += n;
+ }
+
+ return ERROR_OUT_OF_RANGE;
+}
+
+status_t SampleTable::findClosestSample(
+ uint32_t req_time, uint32_t *sample_index, uint32_t flags) {
+ Mutex::Autolock autoLock(mLock);
+
+ uint32_t cur_sample = 0;
+ uint32_t time = 0;
+ for (uint32_t i = 0; i < mTimeToSampleCount; ++i) {
+ uint32_t n = mTimeToSample[2 * i];
+ uint32_t delta = mTimeToSample[2 * i + 1];
+
+ if (req_time < time + n * delta) {
+ int j = (req_time - time) / delta;
+
+ *sample_index = cur_sample + j;
+
+ if (flags & kSyncSample_Flag) {
+ return findClosestSyncSample(*sample_index, sample_index);
+ }
+
+ return OK;
+ }
+
+ time += delta * n;
+ cur_sample += n;
+ }
+
+ return ERROR_OUT_OF_RANGE;
+}
+
+status_t SampleTable::findClosestSyncSample(
+ uint32_t start_sample_index, uint32_t *sample_index) {
+ *sample_index = 0;
+
+ if (mSyncSampleOffset < 0) {
+ // All samples are sync-samples.
+ *sample_index = start_sample_index;
+ return OK;
+ }
+
+ uint32_t x;
+ uint32_t left = 0;
+ uint32_t right = mNumSyncSamples;
+ while (left < right) {
+ uint32_t mid = (left + right) / 2;
+ if (mDataSource->read_at(
+ mSyncSampleOffset + 8 + (mid - 1) * 4, &x, 4) != 4) {
+ return ERROR_IO;
+ }
+
+ x = ntohl(x);
+
+ if (x < (start_sample_index + 1)) {
+ left = mid + 1;
+ } else if (x > (start_sample_index + 1)) {
+ right = mid;
+ } else {
+ break;
+ }
+ }
+
+#if 1
+ // Make sure we return a sample at or _after_ the requested one.
+ // Seeking to a particular time in a media source containing audio and
+ // video will most likely be able to sync fairly close to the requested
+ // time in the audio track but may only be able to seek a fair distance
+ // from the requested time in the video track.
+ // If we seek the video track to a time earlier than the audio track,
+ // we'll cause the video track to be late for quite a while, the decoder
+ // trying to catch up.
+ // If we seek the video track to a time later than the audio track,
+ // audio will start playing fine while no video will be output for a
+ // while, the video decoder will not stress the system.
+ if (mDataSource->read_at(
+ mSyncSampleOffset + 8 + (left - 1) * 4, &x, 4) != 4) {
+ return ERROR_IO;
+ }
+ x = ntohl(x);
+ assert((x - 1) >= start_sample_index);
+#endif
+
+ *sample_index = x - 1;
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/ShoutcastSource.cpp b/media/libstagefright/ShoutcastSource.cpp
new file mode 100644
index 0000000..17b626e
--- /dev/null
+++ b/media/libstagefright/ShoutcastSource.cpp
@@ -0,0 +1,154 @@
+/*
+ * Copyright (C) 2009 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 <stdlib.h>
+
+#include <media/stagefright/HTTPStream.h>
+#include <media/stagefright/MediaBuffer.h>
+#include <media/stagefright/MediaBufferGroup.h>
+#include <media/stagefright/MetaData.h>
+#include <media/stagefright/ShoutcastSource.h>
+#include <media/stagefright/string.h>
+
+namespace android {
+
+ShoutcastSource::ShoutcastSource(HTTPStream *http)
+ : mHttp(http),
+ mMetaDataOffset(0),
+ mBytesUntilMetaData(0),
+ mGroup(NULL),
+ mStarted(false) {
+ string metaint;
+ if (mHttp->find_header_value("icy-metaint", &metaint)) {
+ char *end;
+ const char *start = metaint.c_str();
+ mMetaDataOffset = strtol(start, &end, 10);
+ assert(end > start && *end == '\0');
+ assert(mMetaDataOffset > 0);
+
+ mBytesUntilMetaData = mMetaDataOffset;
+ }
+}
+
+ShoutcastSource::~ShoutcastSource() {
+ if (mStarted) {
+ stop();
+ }
+
+ delete mHttp;
+ mHttp = NULL;
+}
+
+status_t ShoutcastSource::start(MetaData *) {
+ assert(!mStarted);
+
+ mGroup = new MediaBufferGroup;
+ mGroup->add_buffer(new MediaBuffer(4096)); // XXX
+
+ mStarted = true;
+
+ return OK;
+}
+
+status_t ShoutcastSource::stop() {
+ assert(mStarted);
+
+ delete mGroup;
+ mGroup = NULL;
+
+ mStarted = false;
+
+ return OK;
+}
+
+sp<MetaData> ShoutcastSource::getFormat() {
+ sp<MetaData> meta = new MetaData;
+ meta->setCString(kKeyMIMEType, "audio/mpeg");
+ meta->setInt32(kKeySampleRate, 44100);
+ meta->setInt32(kKeyChannelCount, 2); // XXX
+
+ return meta;
+}
+
+status_t ShoutcastSource::read(
+ MediaBuffer **out, const ReadOptions *options) {
+ assert(mStarted);
+
+ *out = NULL;
+
+ int64_t seekTimeUs;
+ if (options && options->getSeekTo(&seekTimeUs)) {
+ return ERROR_UNSUPPORTED;
+ }
+
+ MediaBuffer *buffer;
+ status_t err = mGroup->acquire_buffer(&buffer);
+ if (err != OK) {
+ return err;
+ }
+
+ *out = buffer;
+
+ size_t num_bytes = buffer->size();
+ if (mMetaDataOffset > 0 && num_bytes > mBytesUntilMetaData) {
+ num_bytes = mBytesUntilMetaData;
+ }
+
+ ssize_t n = mHttp->receive(buffer->data(), num_bytes);
+
+ if (n <= 0) {
+ return (status_t)n;
+ }
+
+ buffer->set_range(0, n);
+
+ mBytesUntilMetaData -= (size_t)n;
+
+ if (mBytesUntilMetaData == 0) {
+ unsigned char num_16_byte_blocks = 0;
+ n = mHttp->receive((char *)&num_16_byte_blocks, 1);
+ assert(n == 1);
+
+ char meta[255 * 16];
+ size_t meta_size = num_16_byte_blocks * 16;
+ size_t meta_length = 0;
+ while (meta_length < meta_size) {
+ n = mHttp->receive(&meta[meta_length], meta_size - meta_length);
+ if (n <= 0) {
+ return (status_t)n;
+ }
+
+ meta_length += (size_t) n;
+ }
+
+ while (meta_length > 0 && meta[meta_length - 1] == '\0') {
+ --meta_length;
+ }
+
+ if (meta_length > 0) {
+ // Technically we should probably attach this meta data to the
+ // next buffer. XXX
+ buffer->meta_data()->setData('shou', 'shou', meta, meta_length);
+ }
+
+ mBytesUntilMetaData = mMetaDataOffset;
+ }
+
+ return OK;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/SoftwareRenderer.cpp b/media/libstagefright/SoftwareRenderer.cpp
new file mode 100644
index 0000000..66b6b07
--- /dev/null
+++ b/media/libstagefright/SoftwareRenderer.cpp
@@ -0,0 +1,173 @@
+/*
+ * Copyright (C) 2009 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_TAG "SoftwareRenderer"
+#include <utils/Log.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <binder/MemoryHeapBase.h>
+#include <media/stagefright/SoftwareRenderer.h>
+#include <ui/ISurface.h>
+
+namespace android {
+
+#define QCOM_YUV 0
+
+SoftwareRenderer::SoftwareRenderer(
+ const sp<ISurface> &surface,
+ size_t displayWidth, size_t displayHeight,
+ size_t decodedWidth, size_t decodedHeight)
+ : mISurface(surface),
+ mDisplayWidth(displayWidth),
+ mDisplayHeight(displayHeight),
+ mDecodedWidth(decodedWidth),
+ mDecodedHeight(decodedHeight),
+ mFrameSize(mDecodedWidth * mDecodedHeight * 2), // RGB565
+ mMemoryHeap(new MemoryHeapBase(2 * mFrameSize)),
+ mIndex(0) {
+ assert(mISurface.get() != NULL);
+ assert(mDecodedWidth > 0);
+ assert(mDecodedHeight > 0);
+ assert(mMemoryHeap->heapID() >= 0);
+
+ ISurface::BufferHeap bufferHeap(
+ mDisplayWidth, mDisplayHeight,
+ mDecodedWidth, mDecodedHeight,
+ PIXEL_FORMAT_RGB_565,
+ mMemoryHeap);
+
+ status_t err = mISurface->registerBuffers(bufferHeap);
+ assert(err == OK);
+}
+
+SoftwareRenderer::~SoftwareRenderer() {
+ mISurface->unregisterBuffers();
+}
+
+void SoftwareRenderer::render(
+ const void *data, size_t size, void *platformPrivate) {
+ assert(size >= (mDecodedWidth * mDecodedHeight * 3) / 2);
+
+ static const signed kClipMin = -278;
+ static const signed kClipMax = 535;
+ static uint8_t kClip[kClipMax - kClipMin + 1];
+ static uint8_t *kAdjustedClip = &kClip[-kClipMin];
+
+ static bool clipInitialized = false;
+
+ if (!clipInitialized) {
+ for (signed i = kClipMin; i <= kClipMax; ++i) {
+ kClip[i - kClipMin] = (i < 0) ? 0 : (i > 255) ? 255 : (uint8_t)i;
+ }
+ clipInitialized = true;
+ }
+
+ size_t offset = mIndex * mFrameSize;
+
+ void *dst = (uint8_t *)mMemoryHeap->getBase() + offset;
+
+ uint32_t *dst_ptr = (uint32_t *)dst;
+
+ const uint8_t *src_y = (const uint8_t *)data;
+
+ const uint8_t *src_u =
+ (const uint8_t *)src_y + mDecodedWidth * mDecodedHeight;
+
+#if !QCOM_YUV
+ const uint8_t *src_v =
+ (const uint8_t *)src_u + (mDecodedWidth / 2) * (mDecodedHeight / 2);
+#endif
+
+ for (size_t y = 0; y < mDecodedHeight; ++y) {
+ for (size_t x = 0; x < mDecodedWidth; x += 2) {
+ // B = 1.164 * (Y - 16) + 2.018 * (U - 128)
+ // G = 1.164 * (Y - 16) - 0.813 * (V - 128) - 0.391 * (U - 128)
+ // R = 1.164 * (Y - 16) + 1.596 * (V - 128)
+
+ // B = 298/256 * (Y - 16) + 517/256 * (U - 128)
+ // G = .................. - 208/256 * (V - 128) - 100/256 * (U - 128)
+ // R = .................. + 409/256 * (V - 128)
+
+ // min_B = (298 * (- 16) + 517 * (- 128)) / 256 = -277
+ // min_G = (298 * (- 16) - 208 * (255 - 128) - 100 * (255 - 128)) / 256 = -172
+ // min_R = (298 * (- 16) + 409 * (- 128)) / 256 = -223
+
+ // max_B = (298 * (255 - 16) + 517 * (255 - 128)) / 256 = 534
+ // max_G = (298 * (255 - 16) - 208 * (- 128) - 100 * (- 128)) / 256 = 432
+ // max_R = (298 * (255 - 16) + 409 * (255 - 128)) / 256 = 481
+
+ // clip range -278 .. 535
+
+ signed y1 = (signed)src_y[x] - 16;
+ signed y2 = (signed)src_y[x + 1] - 16;
+
+#if QCOM_YUV
+ signed u = (signed)src_u[x & ~1] - 128;
+ signed v = (signed)src_u[(x & ~1) + 1] - 128;
+#else
+ signed u = (signed)src_u[x / 2] - 128;
+ signed v = (signed)src_v[x / 2] - 128;
+#endif
+
+ signed u_b = u * 517;
+ signed u_g = -u * 100;
+ signed v_g = -v * 208;
+ signed v_r = v * 409;
+
+ signed tmp1 = y1 * 298;
+ signed b1 = (tmp1 + u_b) / 256;
+ signed g1 = (tmp1 + v_g + u_g) / 256;
+ signed r1 = (tmp1 + v_r) / 256;
+
+ signed tmp2 = y2 * 298;
+ signed b2 = (tmp2 + u_b) / 256;
+ signed g2 = (tmp2 + v_g + u_g) / 256;
+ signed r2 = (tmp2 + v_r) / 256;
+
+ uint32_t rgb1 =
+ ((kAdjustedClip[r1] >> 3) << 11)
+ | ((kAdjustedClip[g1] >> 2) << 5)
+ | (kAdjustedClip[b1] >> 3);
+
+ uint32_t rgb2 =
+ ((kAdjustedClip[r2] >> 3) << 11)
+ | ((kAdjustedClip[g2] >> 2) << 5)
+ | (kAdjustedClip[b2] >> 3);
+
+ dst_ptr[x / 2] = (rgb2 << 16) | rgb1;
+ }
+
+ src_y += mDecodedWidth;
+
+ if (y & 1) {
+#if QCOM_YUV
+ src_u += mDecodedWidth;
+#else
+ src_u += mDecodedWidth / 2;
+ src_v += mDecodedWidth / 2;
+#endif
+ }
+
+ dst_ptr += mDecodedWidth / 2;
+ }
+
+ mISurface->postBuffer(offset);
+ mIndex = 1 - mIndex;
+}
+
+} // namespace android
diff --git a/media/libstagefright/SurfaceRenderer.cpp b/media/libstagefright/SurfaceRenderer.cpp
new file mode 100644
index 0000000..e54288d
--- /dev/null
+++ b/media/libstagefright/SurfaceRenderer.cpp
@@ -0,0 +1,69 @@
+/*
+ * Copyright (C) 2009 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_TAG "SurfaceRenderer"
+#include <utils/Log.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <media/stagefright/SurfaceRenderer.h>
+#include <ui/Surface.h>
+
+namespace android {
+
+SurfaceRenderer::SurfaceRenderer(
+ const sp<Surface> &surface,
+ size_t displayWidth, size_t displayHeight,
+ size_t decodedWidth, size_t decodedHeight)
+ : mSurface(surface),
+ mDisplayWidth(displayWidth),
+ mDisplayHeight(displayHeight),
+ mDecodedWidth(decodedWidth),
+ mDecodedHeight(decodedHeight) {
+}
+
+SurfaceRenderer::~SurfaceRenderer() {
+}
+
+void SurfaceRenderer::render(
+ const void *data, size_t size, void *platformPrivate) {
+ Surface::SurfaceInfo info;
+ status_t err = mSurface->lock(&info);
+ if (err != OK) {
+ return;
+ }
+
+ const uint8_t *src = (const uint8_t *)data;
+ uint8_t *dst = (uint8_t *)info.bits;
+
+ for (size_t i = 0; i < mDisplayHeight; ++i) {
+ memcpy(dst, src, mDisplayWidth);
+ src += mDecodedWidth;
+ dst += mDisplayWidth;
+ }
+ src += (mDecodedHeight - mDisplayHeight) * mDecodedWidth;
+
+ for (size_t i = 0; i < (mDisplayHeight + 1) / 2; ++i) {
+ memcpy(dst, src, (mDisplayWidth + 1) & ~1);
+ src += (mDecodedWidth + 1) & ~1;
+ dst += (mDisplayWidth + 1) & ~1;
+ }
+
+ mSurface->unlockAndPost();
+}
+
+} // namespace android
diff --git a/media/libstagefright/TimeSource.cpp b/media/libstagefright/TimeSource.cpp
new file mode 100644
index 0000000..7deb310
--- /dev/null
+++ b/media/libstagefright/TimeSource.cpp
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2009 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 <sys/time.h>
+
+#include <media/stagefright/TimeSource.h>
+
+namespace android {
+
+SystemTimeSource::SystemTimeSource()
+ : mStartTimeUs(GetSystemTimeUs()) {
+}
+
+int64_t SystemTimeSource::getRealTimeUs() {
+ return GetSystemTimeUs() - mStartTimeUs;
+}
+
+// static
+int64_t SystemTimeSource::GetSystemTimeUs() {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+
+ return (int64_t)tv.tv_sec * 1000000 + tv.tv_usec;
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/TimedEventQueue.cpp b/media/libstagefright/TimedEventQueue.cpp
new file mode 100644
index 0000000..10cf81a
--- /dev/null
+++ b/media/libstagefright/TimedEventQueue.cpp
@@ -0,0 +1,206 @@
+/*
+ * Copyright (C) 2009 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.
+ */
+
+#undef __STRICT_ANSI__
+#define __STDINT_LIMITS
+#include <stdint.h>
+
+#define LOG_TAG "TimedEventQueue"
+#include <utils/Log.h>
+
+#include <sys/time.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include <media/stagefright/TimedEventQueue.h>
+
+namespace android {
+
+TimedEventQueue::TimedEventQueue()
+ : mRunning(false),
+ mStopped(false) {
+}
+
+TimedEventQueue::~TimedEventQueue() {
+ stop();
+}
+
+void TimedEventQueue::start() {
+ if (mRunning) {
+ return;
+ }
+
+ mStopped = false;
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ pthread_create(&mThread, &attr, ThreadWrapper, this);
+
+ pthread_attr_destroy(&attr);
+
+ mRunning = true;
+}
+
+void TimedEventQueue::stop(bool flush) {
+ if (!mRunning) {
+ return;
+ }
+
+ if (flush) {
+ postEventToBack(new StopEvent);
+ } else {
+ postTimedEvent(new StopEvent, INT64_MIN);
+ }
+
+ void *dummy;
+ pthread_join(mThread, &dummy);
+
+ mQueue.clear();
+
+ mRunning = false;
+}
+
+void TimedEventQueue::postEvent(const sp<Event> &event) {
+ // Reserve an earlier timeslot an INT64_MIN to be able to post
+ // the StopEvent to the absolute head of the queue.
+ postTimedEvent(event, INT64_MIN + 1);
+}
+
+void TimedEventQueue::postEventToBack(const sp<Event> &event) {
+ postTimedEvent(event, INT64_MAX);
+}
+
+void TimedEventQueue::postEventWithDelay(
+ const sp<Event> &event, int64_t delay_us) {
+ assert(delay_us >= 0);
+ postTimedEvent(event, getRealTimeUs() + delay_us);
+}
+
+void TimedEventQueue::postTimedEvent(
+ const sp<Event> &event, int64_t realtime_us) {
+ Mutex::Autolock autoLock(mLock);
+
+ List<QueueItem>::iterator it = mQueue.begin();
+ while (it != mQueue.end() && realtime_us >= (*it).realtime_us) {
+ ++it;
+ }
+
+ QueueItem item;
+ item.event = event;
+ item.realtime_us = realtime_us;
+
+ if (it == mQueue.begin()) {
+ mQueueHeadChangedCondition.signal();
+ }
+
+ mQueue.insert(it, item);
+
+ mQueueNotEmptyCondition.signal();
+}
+
+bool TimedEventQueue::cancelEvent(const sp<Event> &event) {
+ Mutex::Autolock autoLock(mLock);
+
+ List<QueueItem>::iterator it = mQueue.begin();
+ while (it != mQueue.end() && (*it).event != event) {
+ ++it;
+ }
+
+ if (it == mQueue.end()) {
+ return false;
+ }
+
+ if (it == mQueue.begin()) {
+ mQueueHeadChangedCondition.signal();
+ }
+
+ mQueue.erase(it);
+
+ return true;
+}
+
+// static
+int64_t TimedEventQueue::getRealTimeUs() {
+ struct timeval tv;
+ gettimeofday(&tv, NULL);
+
+ return (int64_t)tv.tv_sec * 1000000 + tv.tv_usec;
+}
+
+// static
+void *TimedEventQueue::ThreadWrapper(void *me) {
+ static_cast<TimedEventQueue *>(me)->threadEntry();
+
+ return NULL;
+}
+
+void TimedEventQueue::threadEntry() {
+ for (;;) {
+ int64_t now_us;
+ sp<Event> event;
+
+ {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mStopped) {
+ break;
+ }
+
+ while (mQueue.empty()) {
+ mQueueNotEmptyCondition.wait(mLock);
+ }
+
+ List<QueueItem>::iterator it;
+ for (;;) {
+ it = mQueue.begin();
+
+ now_us = getRealTimeUs();
+ int64_t when_us = (*it).realtime_us;
+
+ int64_t delay_us;
+ if (when_us < 0 || when_us == INT64_MAX) {
+ delay_us = 0;
+ } else {
+ delay_us = when_us - now_us;
+ }
+
+ if (delay_us <= 0) {
+ break;
+ }
+
+ status_t err = mQueueHeadChangedCondition.waitRelative(
+ mLock, delay_us * 1000);
+
+ if (err == -ETIMEDOUT) {
+ now_us = getRealTimeUs();
+ break;
+ }
+ }
+
+ event = (*it).event;
+ mQueue.erase(it);
+ }
+
+ // Fire event with the lock NOT held.
+ event->fire(this, now_us);
+ }
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/Utils.cpp b/media/libstagefright/Utils.cpp
new file mode 100644
index 0000000..2720f93
--- /dev/null
+++ b/media/libstagefright/Utils.cpp
@@ -0,0 +1,45 @@
+/*
+ * Copyright (C) 2009 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 <arpa/inet.h>
+
+#include <media/stagefright/Utils.h>
+
+namespace android {
+
+uint16_t U16_AT(const uint8_t *ptr) {
+ return ptr[0] << 8 | ptr[1];
+}
+
+uint32_t U32_AT(const uint8_t *ptr) {
+ return ptr[0] << 24 | ptr[1] << 16 | ptr[2] << 8 | ptr[3];
+}
+
+uint64_t U64_AT(const uint8_t *ptr) {
+ return ((uint64_t)U32_AT(ptr)) << 32 | U32_AT(ptr + 4);
+}
+
+// XXX warning: these won't work on big-endian host.
+uint64_t ntoh64(uint64_t x) {
+ return ((uint64_t)ntohl(x & 0xffffffff) << 32) | ntohl(x >> 32);
+}
+
+uint64_t hton64(uint64_t x) {
+ return ((uint64_t)htonl(x & 0xffffffff) << 32) | htonl(x >> 32);
+}
+
+} // namespace android
+
diff --git a/media/libstagefright/omx/Android.mk b/media/libstagefright/omx/Android.mk
new file mode 100644
index 0000000..9c6d475
--- /dev/null
+++ b/media/libstagefright/omx/Android.mk
@@ -0,0 +1,27 @@
+ifeq ($(BUILD_WITH_STAGEFRIGHT),true)
+
+LOCAL_PATH:= $(call my-dir)
+include $(CLEAR_VARS)
+
+# Set up the OpenCore variables.
+include external/opencore/Config.mk
+LOCAL_C_INCLUDES := $(PV_INCLUDES)
+LOCAL_CFLAGS := $(PV_CFLAGS_MINUS_VISIBILITY)
+
+LOCAL_SRC_FILES:= \
+ OMX.cpp
+
+LOCAL_SHARED_LIBRARIES := \
+ libbinder \
+ libmedia \
+ libutils \
+ libui \
+ libopencore_common
+
+LOCAL_PRELINK_MODULE:= false
+
+LOCAL_MODULE:= libstagefright_omx
+
+include $(BUILD_SHARED_LIBRARY)
+
+endif
diff --git a/media/libstagefright/omx/OMX.cpp b/media/libstagefright/omx/OMX.cpp
new file mode 100644
index 0000000..c18f5ce
--- /dev/null
+++ b/media/libstagefright/omx/OMX.cpp
@@ -0,0 +1,621 @@
+/*
+ * Copyright (C) 2009 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 "OMX"
+#include <utils/Log.h>
+
+#include <sys/socket.h>
+
+#undef NDEBUG
+#include <assert.h>
+
+#include "OMX.h"
+#include "pv_omxcore.h"
+
+#include <binder/IMemory.h>
+
+#include <OMX_Component.h>
+
+namespace android {
+
+class NodeMeta {
+public:
+ NodeMeta(OMX *owner)
+ : mOwner(owner),
+ mHandle(NULL) {
+ }
+
+ OMX *owner() const {
+ return mOwner;
+ }
+
+ void setHandle(OMX_HANDLETYPE handle) {
+ assert(mHandle == NULL);
+ mHandle = handle;
+ }
+
+ OMX_HANDLETYPE handle() const {
+ return mHandle;
+ }
+
+ void setObserver(const sp<IOMXObserver> &observer) {
+ mObserver = observer;
+ }
+
+ sp<IOMXObserver> observer() {
+ return mObserver;
+ }
+
+private:
+ OMX *mOwner;
+ OMX_HANDLETYPE mHandle;
+ sp<IOMXObserver> mObserver;
+
+ NodeMeta(const NodeMeta &);
+ NodeMeta &operator=(const NodeMeta &);
+};
+
+class BufferMeta {
+public:
+ BufferMeta(OMX *owner, const sp<IMemory> &mem, bool is_backup = false)
+ : mOwner(owner),
+ mMem(mem),
+ mIsBackup(is_backup) {
+ }
+
+ BufferMeta(OMX *owner, size_t size)
+ : mOwner(owner),
+ mSize(size),
+ mIsBackup(false) {
+ }
+
+ void CopyFromOMX(const OMX_BUFFERHEADERTYPE *header) {
+ if (!mIsBackup) {
+ return;
+ }
+
+ memcpy((OMX_U8 *)mMem->pointer() + header->nOffset,
+ header->pBuffer + header->nOffset,
+ header->nFilledLen);
+ }
+
+ void CopyToOMX(const OMX_BUFFERHEADERTYPE *header) {
+ if (!mIsBackup) {
+ return;
+ }
+
+ memcpy(header->pBuffer + header->nOffset,
+ (const OMX_U8 *)mMem->pointer() + header->nOffset,
+ header->nFilledLen);
+ }
+
+private:
+ OMX *mOwner;
+ sp<IMemory> mMem;
+ size_t mSize;
+ bool mIsBackup;
+
+ BufferMeta(const BufferMeta &);
+ BufferMeta &operator=(const BufferMeta &);
+};
+
+// static
+OMX_CALLBACKTYPE OMX::kCallbacks = {
+ &OnEvent, &OnEmptyBufferDone, &OnFillBufferDone
+};
+
+// static
+OMX_ERRORTYPE OMX::OnEvent(
+ OMX_IN OMX_HANDLETYPE hComponent,
+ OMX_IN OMX_PTR pAppData,
+ OMX_IN OMX_EVENTTYPE eEvent,
+ OMX_IN OMX_U32 nData1,
+ OMX_IN OMX_U32 nData2,
+ OMX_IN OMX_PTR pEventData) {
+ NodeMeta *meta = static_cast<NodeMeta *>(pAppData);
+ return meta->owner()->OnEvent(meta, eEvent, nData1, nData2, pEventData);
+}
+
+// static
+OMX_ERRORTYPE OMX::OnEmptyBufferDone(
+ OMX_IN OMX_HANDLETYPE hComponent,
+ OMX_IN OMX_PTR pAppData,
+ OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
+ NodeMeta *meta = static_cast<NodeMeta *>(pAppData);
+ return meta->owner()->OnEmptyBufferDone(meta, pBuffer);
+}
+
+// static
+OMX_ERRORTYPE OMX::OnFillBufferDone(
+ OMX_IN OMX_HANDLETYPE hComponent,
+ OMX_IN OMX_PTR pAppData,
+ OMX_IN OMX_BUFFERHEADERTYPE* pBuffer) {
+ NodeMeta *meta = static_cast<NodeMeta *>(pAppData);
+ return meta->owner()->OnFillBufferDone(meta, pBuffer);
+}
+
+OMX::OMX()
+#if IOMX_USES_SOCKETS
+ : mSock(-1)
+#endif
+{
+}
+
+OMX::~OMX() {
+#if IOMX_USES_SOCKETS
+ assert(mSock < 0);
+#endif
+}
+
+#if IOMX_USES_SOCKETS
+status_t OMX::connect(int *sd) {
+ Mutex::Autolock autoLock(mLock);
+
+ if (mSock >= 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ int sockets[2];
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sockets) < 0) {
+ return UNKNOWN_ERROR;
+ }
+
+ mSock = sockets[0];
+ *sd = sockets[1];
+
+ pthread_attr_t attr;
+ pthread_attr_init(&attr);
+ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE);
+
+ int err = pthread_create(&mThread, &attr, ThreadWrapper, this);
+ assert(err == 0);
+
+ pthread_attr_destroy(&attr);
+
+ return OK;
+}
+
+// static
+void *OMX::ThreadWrapper(void *me) {
+ ((OMX *)me)->threadEntry();
+
+ return NULL;
+}
+
+void OMX::threadEntry() {
+ bool done = false;
+ while (!done) {
+ omx_message msg;
+ ssize_t n = recv(mSock, &msg, sizeof(msg), 0);
+
+ if (n <= 0) {
+ break;
+ }
+
+ Mutex::Autolock autoLock(mLock);
+
+ switch (msg.type) {
+ case omx_message::FILL_BUFFER:
+ {
+ OMX_BUFFERHEADERTYPE *header =
+ static_cast<OMX_BUFFERHEADERTYPE *>(
+ msg.u.buffer_data.buffer);
+
+ header->nFilledLen = 0;
+ header->nOffset = 0;
+ header->nFlags = 0;
+
+ NodeMeta *node_meta = static_cast<NodeMeta *>(
+ msg.u.buffer_data.node);
+
+ LOGV("FillThisBuffer buffer=%p", header);
+
+ OMX_ERRORTYPE err =
+ OMX_FillThisBuffer(node_meta->handle(), header);
+ assert(err == OMX_ErrorNone);
+ break;
+ }
+
+ case omx_message::EMPTY_BUFFER:
+ {
+ OMX_BUFFERHEADERTYPE *header =
+ static_cast<OMX_BUFFERHEADERTYPE *>(
+ msg.u.extended_buffer_data.buffer);
+
+ header->nFilledLen = msg.u.extended_buffer_data.range_length;
+ header->nOffset = msg.u.extended_buffer_data.range_offset;
+ header->nFlags = msg.u.extended_buffer_data.flags;
+ header->nTimeStamp = msg.u.extended_buffer_data.timestamp;
+
+ BufferMeta *buffer_meta =
+ static_cast<BufferMeta *>(header->pAppPrivate);
+ buffer_meta->CopyToOMX(header);
+
+ NodeMeta *node_meta = static_cast<NodeMeta *>(
+ msg.u.extended_buffer_data.node);
+
+ LOGV("EmptyThisBuffer buffer=%p", header);
+
+ OMX_ERRORTYPE err =
+ OMX_EmptyThisBuffer(node_meta->handle(), header);
+ assert(err == OMX_ErrorNone);
+ break;
+ }
+
+ case omx_message::SEND_COMMAND:
+ {
+ NodeMeta *node_meta = static_cast<NodeMeta *>(
+ msg.u.send_command_data.node);
+
+ OMX_ERRORTYPE err =
+ OMX_SendCommand(
+ node_meta->handle(), msg.u.send_command_data.cmd,
+ msg.u.send_command_data.param, NULL);
+ assert(err == OMX_ErrorNone);
+ break;
+ }
+
+ case omx_message::DISCONNECT:
+ {
+ omx_message msg;
+ msg.type = omx_message::DISCONNECTED;
+ ssize_t n = send(mSock, &msg, sizeof(msg), 0);
+ assert(n > 0 && static_cast<size_t>(n) == sizeof(msg));
+ done = true;
+ break;
+ }
+
+ default:
+ LOGE("received unknown omx_message type %d", msg.type);
+ break;
+ }
+ }
+
+ Mutex::Autolock autoLock(mLock);
+ close(mSock);
+ mSock = -1;
+}
+#endif
+
+status_t OMX::list_nodes(List<String8> *list) {
+ OMX_MasterInit(); // XXX Put this somewhere else.
+
+ list->clear();
+
+ OMX_U32 index = 0;
+ char componentName[256];
+ while (OMX_MasterComponentNameEnum(componentName, sizeof(componentName), index)
+ == OMX_ErrorNone) {
+ list->push_back(String8(componentName));
+
+ ++index;
+ }
+
+ return OK;
+}
+
+status_t OMX::allocate_node(const char *name, node_id *node) {
+ Mutex::Autolock autoLock(mLock);
+
+ *node = 0;
+
+ OMX_MasterInit(); // XXX Put this somewhere else.
+
+ NodeMeta *meta = new NodeMeta(this);
+
+ OMX_HANDLETYPE handle;
+ OMX_ERRORTYPE err = OMX_MasterGetHandle(
+ &handle, const_cast<char *>(name), meta, &kCallbacks);
+
+ if (err != OMX_ErrorNone) {
+ LOGE("FAILED to allocate omx component '%s'", name);
+
+ delete meta;
+ meta = NULL;
+
+ return UNKNOWN_ERROR;
+ }
+
+ meta->setHandle(handle);
+
+ *node = meta;
+
+ return OK;
+}
+
+status_t OMX::free_node(node_id node) {
+ Mutex::Autolock autoLock(mLock);
+
+ NodeMeta *meta = static_cast<NodeMeta *>(node);
+
+ OMX_ERRORTYPE err = OMX_MasterFreeHandle(meta->handle());
+
+ delete meta;
+ meta = NULL;
+
+ return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK;
+}
+
+status_t OMX::send_command(
+ node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param) {
+ Mutex::Autolock autoLock(mLock);
+
+#if IOMX_USES_SOCKETS
+ if (mSock < 0) {
+ return UNKNOWN_ERROR;
+ }
+#endif
+
+ NodeMeta *meta = static_cast<NodeMeta *>(node);
+ OMX_ERRORTYPE err = OMX_SendCommand(meta->handle(), cmd, param, NULL);
+
+ return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK;
+}
+
+status_t OMX::get_parameter(
+ node_id node, OMX_INDEXTYPE index,
+ void *params, size_t size) {
+ Mutex::Autolock autoLock(mLock);
+
+ NodeMeta *meta = static_cast<NodeMeta *>(node);
+ OMX_ERRORTYPE err = OMX_GetParameter(meta->handle(), index, params);
+
+ return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK;
+}
+
+status_t OMX::set_parameter(
+ node_id node, OMX_INDEXTYPE index,
+ const void *params, size_t size) {
+ Mutex::Autolock autoLock(mLock);
+
+ NodeMeta *meta = static_cast<NodeMeta *>(node);
+ OMX_ERRORTYPE err =
+ OMX_SetParameter(meta->handle(), index, const_cast<void *>(params));
+
+ return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK;
+}
+
+status_t OMX::use_buffer(
+ node_id node, OMX_U32 port_index, const sp<IMemory> &params,
+ buffer_id *buffer) {
+ Mutex::Autolock autoLock(mLock);
+
+ BufferMeta *buffer_meta = new BufferMeta(this, params);
+
+ OMX_BUFFERHEADERTYPE *header;
+
+ NodeMeta *node_meta = static_cast<NodeMeta *>(node);
+ OMX_ERRORTYPE err =
+ OMX_UseBuffer(node_meta->handle(), &header, port_index, buffer_meta,
+ params->size(), static_cast<OMX_U8 *>(params->pointer()));
+
+ if (err != OMX_ErrorNone) {
+ LOGE("OMX_UseBuffer failed with error %d (0x%08x)", err, err);
+
+ delete buffer_meta;
+ buffer_meta = NULL;
+
+ *buffer = 0;
+ return UNKNOWN_ERROR;
+ }
+
+ *buffer = header;
+
+ return OK;
+}
+
+status_t OMX::allocate_buffer(
+ node_id node, OMX_U32 port_index, size_t size,
+ buffer_id *buffer) {
+ Mutex::Autolock autoLock(mLock);
+
+ BufferMeta *buffer_meta = new BufferMeta(this, size);
+
+ OMX_BUFFERHEADERTYPE *header;
+
+ NodeMeta *node_meta = static_cast<NodeMeta *>(node);
+ OMX_ERRORTYPE err =
+ OMX_AllocateBuffer(node_meta->handle(), &header, port_index,
+ buffer_meta, size);
+
+ if (err != OMX_ErrorNone) {
+ delete buffer_meta;
+ buffer_meta = NULL;
+
+ *buffer = 0;
+ return UNKNOWN_ERROR;
+ }
+
+ *buffer = header;
+
+ return OK;
+}
+
+status_t OMX::allocate_buffer_with_backup(
+ node_id node, OMX_U32 port_index, const sp<IMemory> &params,
+ buffer_id *buffer) {
+ Mutex::Autolock autoLock(mLock);
+
+ BufferMeta *buffer_meta = new BufferMeta(this, params, true);
+
+ OMX_BUFFERHEADERTYPE *header;
+
+ NodeMeta *node_meta = static_cast<NodeMeta *>(node);
+ OMX_ERRORTYPE err =
+ OMX_AllocateBuffer(
+ node_meta->handle(), &header, port_index, buffer_meta,
+ params->size());
+
+ if (err != OMX_ErrorNone) {
+ delete buffer_meta;
+ buffer_meta = NULL;
+
+ *buffer = 0;
+ return UNKNOWN_ERROR;
+ }
+
+ *buffer = header;
+
+ return OK;
+}
+
+status_t OMX::free_buffer(node_id node, OMX_U32 port_index, buffer_id buffer) {
+ OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer;
+ BufferMeta *buffer_meta = static_cast<BufferMeta *>(header->pAppPrivate);
+
+ NodeMeta *node_meta = static_cast<NodeMeta *>(node);
+ OMX_ERRORTYPE err =
+ OMX_FreeBuffer(node_meta->handle(), port_index, header);
+
+ delete buffer_meta;
+ buffer_meta = NULL;
+
+ return (err != OMX_ErrorNone) ? UNKNOWN_ERROR : OK;
+}
+
+OMX_ERRORTYPE OMX::OnEvent(
+ NodeMeta *meta,
+ OMX_IN OMX_EVENTTYPE eEvent,
+ OMX_IN OMX_U32 nData1,
+ OMX_IN OMX_U32 nData2,
+ OMX_IN OMX_PTR pEventData) {
+ LOGV("OnEvent(%d, %ld, %ld)", eEvent, nData1, nData2);
+
+ omx_message msg;
+ msg.type = omx_message::EVENT;
+ msg.u.event_data.node = meta;
+ msg.u.event_data.event = eEvent;
+ msg.u.event_data.data1 = nData1;
+ msg.u.event_data.data2 = nData2;
+
+#if !IOMX_USES_SOCKETS
+ sp<IOMXObserver> observer = meta->observer();
+ if (observer.get() != NULL) {
+ observer->on_message(msg);
+ }
+#else
+ assert(mSock >= 0);
+
+ ssize_t n = send(mSock, &msg, sizeof(msg), 0);
+ assert(n > 0 && static_cast<size_t>(n) == sizeof(msg));
+#endif
+
+ return OMX_ErrorNone;
+}
+
+OMX_ERRORTYPE OMX::OnEmptyBufferDone(
+ NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer) {
+ LOGV("OnEmptyBufferDone buffer=%p", pBuffer);
+
+ omx_message msg;
+ msg.type = omx_message::EMPTY_BUFFER_DONE;
+ msg.u.buffer_data.node = meta;
+ msg.u.buffer_data.buffer = pBuffer;
+
+#if !IOMX_USES_SOCKETS
+ sp<IOMXObserver> observer = meta->observer();
+ if (observer.get() != NULL) {
+ observer->on_message(msg);
+ }
+#else
+ assert(mSock >= 0);
+ ssize_t n = send(mSock, &msg, sizeof(msg), 0);
+ assert(n > 0 && static_cast<size_t>(n) == sizeof(msg));
+#endif
+
+ return OMX_ErrorNone;
+}
+
+OMX_ERRORTYPE OMX::OnFillBufferDone(
+ NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer) {
+ LOGV("OnFillBufferDone buffer=%p", pBuffer);
+ BufferMeta *buffer_meta = static_cast<BufferMeta *>(pBuffer->pAppPrivate);
+ buffer_meta->CopyFromOMX(pBuffer);
+
+ omx_message msg;
+ msg.type = omx_message::FILL_BUFFER_DONE;
+ msg.u.extended_buffer_data.node = meta;
+ msg.u.extended_buffer_data.buffer = pBuffer;
+ msg.u.extended_buffer_data.range_offset = pBuffer->nOffset;
+ msg.u.extended_buffer_data.range_length = pBuffer->nFilledLen;
+ msg.u.extended_buffer_data.flags = pBuffer->nFlags;
+ msg.u.extended_buffer_data.timestamp = pBuffer->nTimeStamp;
+ msg.u.extended_buffer_data.platform_private = pBuffer->pPlatformPrivate;
+
+#if !IOMX_USES_SOCKETS
+ sp<IOMXObserver> observer = meta->observer();
+ if (observer.get() != NULL) {
+ observer->on_message(msg);
+ }
+#else
+ assert(mSock >= 0);
+
+ ssize_t n = send(mSock, &msg, sizeof(msg), 0);
+ assert(n > 0 && static_cast<size_t>(n) == sizeof(msg));
+#endif
+
+ return OMX_ErrorNone;
+}
+
+#if !IOMX_USES_SOCKETS
+status_t OMX::observe_node(
+ node_id node, const sp<IOMXObserver> &observer) {
+ NodeMeta *node_meta = static_cast<NodeMeta *>(node);
+
+ node_meta->setObserver(observer);
+
+ return OK;
+}
+
+void OMX::fill_buffer(node_id node, buffer_id buffer) {
+ OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer;
+ header->nFilledLen = 0;
+ header->nOffset = 0;
+ header->nFlags = 0;
+
+ NodeMeta *node_meta = static_cast<NodeMeta *>(node);
+
+ OMX_ERRORTYPE err =
+ OMX_FillThisBuffer(node_meta->handle(), header);
+ assert(err == OMX_ErrorNone);
+}
+
+void OMX::empty_buffer(
+ node_id node,
+ buffer_id buffer,
+ OMX_U32 range_offset, OMX_U32 range_length,
+ OMX_U32 flags, OMX_TICKS timestamp) {
+ OMX_BUFFERHEADERTYPE *header = (OMX_BUFFERHEADERTYPE *)buffer;
+ header->nFilledLen = range_length;
+ header->nOffset = range_offset;
+ header->nFlags = flags;
+ header->nTimeStamp = timestamp;
+
+ BufferMeta *buffer_meta =
+ static_cast<BufferMeta *>(header->pAppPrivate);
+ buffer_meta->CopyToOMX(header);
+
+ NodeMeta *node_meta = static_cast<NodeMeta *>(node);
+
+ OMX_ERRORTYPE err =
+ OMX_EmptyThisBuffer(node_meta->handle(), header);
+ assert(err == OMX_ErrorNone);
+}
+#endif
+
+} // namespace android
+
diff --git a/media/libstagefright/omx/OMX.h b/media/libstagefright/omx/OMX.h
new file mode 100644
index 0000000..ed4e5dd
--- /dev/null
+++ b/media/libstagefright/omx/OMX.h
@@ -0,0 +1,132 @@
+/*
+ * Copyright (C) 2009 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 ANDROID_OMX_H_
+#define ANDROID_OMX_H_
+
+#include <pthread.h>
+
+#include <media/IOMX.h>
+#include <utils/threads.h>
+
+namespace android {
+
+class NodeMeta;
+
+class OMX : public BnOMX {
+public:
+ OMX();
+ virtual ~OMX();
+
+#if IOMX_USES_SOCKETS
+ virtual status_t connect(int *sd);
+#endif
+
+ virtual status_t list_nodes(List<String8> *list);
+
+ virtual status_t allocate_node(const char *name, node_id *node);
+ virtual status_t free_node(node_id node);
+
+ virtual status_t send_command(
+ node_id node, OMX_COMMANDTYPE cmd, OMX_S32 param);
+
+ virtual status_t get_parameter(
+ node_id node, OMX_INDEXTYPE index,
+ void *params, size_t size);
+
+ virtual status_t set_parameter(
+ node_id node, OMX_INDEXTYPE index,
+ const void *params, size_t size);
+
+ virtual status_t use_buffer(
+ node_id node, OMX_U32 port_index, const sp<IMemory> &params,
+ buffer_id *buffer);
+
+ virtual status_t allocate_buffer(
+ node_id node, OMX_U32 port_index, size_t size,
+ buffer_id *buffer);
+
+ virtual status_t allocate_buffer_with_backup(
+ node_id node, OMX_U32 port_index, const sp<IMemory> &params,
+ buffer_id *buffer);
+
+ virtual status_t free_buffer(
+ node_id node, OMX_U32 port_index, buffer_id buffer);
+
+#if !IOMX_USES_SOCKETS
+ virtual status_t observe_node(
+ node_id node, const sp<IOMXObserver> &observer);
+
+ virtual void fill_buffer(node_id node, buffer_id buffer);
+
+ virtual void empty_buffer(
+ node_id node,
+ buffer_id buffer,
+ OMX_U32 range_offset, OMX_U32 range_length,
+ OMX_U32 flags, OMX_TICKS timestamp);
+#endif
+
+private:
+ static OMX_CALLBACKTYPE kCallbacks;
+
+#if IOMX_USES_SOCKETS
+ int mSock;
+ pthread_t mThread;
+
+ static void *ThreadWrapper(void *me);
+ void threadEntry();
+#endif
+
+ Mutex mLock;
+
+ static OMX_ERRORTYPE OnEvent(
+ OMX_IN OMX_HANDLETYPE hComponent,
+ OMX_IN OMX_PTR pAppData,
+ OMX_IN OMX_EVENTTYPE eEvent,
+ OMX_IN OMX_U32 nData1,
+ OMX_IN OMX_U32 nData2,
+ OMX_IN OMX_PTR pEventData);
+
+ static OMX_ERRORTYPE OnEmptyBufferDone(
+ OMX_IN OMX_HANDLETYPE hComponent,
+ OMX_IN OMX_PTR pAppData,
+ OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
+
+ static OMX_ERRORTYPE OnFillBufferDone(
+ OMX_IN OMX_HANDLETYPE hComponent,
+ OMX_IN OMX_PTR pAppData,
+ OMX_IN OMX_BUFFERHEADERTYPE* pBuffer);
+
+ OMX_ERRORTYPE OnEvent(
+ NodeMeta *meta,
+ OMX_IN OMX_EVENTTYPE eEvent,
+ OMX_IN OMX_U32 nData1,
+ OMX_IN OMX_U32 nData2,
+ OMX_IN OMX_PTR pEventData);
+
+ OMX_ERRORTYPE OnEmptyBufferDone(
+ NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer);
+
+ OMX_ERRORTYPE OnFillBufferDone(
+ NodeMeta *meta, OMX_IN OMX_BUFFERHEADERTYPE *pBuffer);
+
+ OMX(const OMX &);
+ OMX &operator=(const OMX &);
+};
+
+} // namespace android
+
+#endif // ANDROID_OMX_H_
diff --git a/media/libstagefright/string.cpp b/media/libstagefright/string.cpp
new file mode 100644
index 0000000..5b16784
--- /dev/null
+++ b/media/libstagefright/string.cpp
@@ -0,0 +1,83 @@
+/*
+ * Copyright (C) 2009 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 <media/stagefright/string.h>
+
+namespace android {
+
+// static
+string::size_type string::npos = (string::size_type)-1;
+
+string::string() {
+}
+
+string::string(const char *s, size_t length)
+ : mString(s, length) {
+}
+
+string::string(const string &from, size_type start, size_type length)
+ : mString(from.c_str() + start, length) {
+}
+
+string::string(const char *s)
+ : mString(s) {
+}
+
+const char *string::c_str() const {
+ return mString.string();
+}
+
+string::size_type string::size() const {
+ return mString.length();
+}
+
+void string::clear() {
+ mString = String8();
+}
+
+string::size_type string::find(char c) const {
+ char s[2];
+ s[0] = c;
+ s[1] = '\0';
+
+ ssize_t index = mString.find(s);
+
+ return index < 0 ? npos : (size_type)index;
+}
+
+bool string::operator<(const string &other) const {
+ return mString < other.mString;
+}
+
+bool string::operator==(const string &other) const {
+ return mString == other.mString;
+}
+
+string &string::operator+=(char c) {
+ mString.append(&c, 1);
+
+ return *this;
+}
+
+void string::erase(size_t from, size_t length) {
+ String8 s(mString.string(), from);
+ s.append(mString.string() + from + length);
+
+ mString = s;
+}
+
+} // namespace android
+