From 69d8d46c5985ed76947f875ee3b7d32febfcf201 Mon Sep 17 00:00:00 2001 From: Stefan Ekenberg Date: Mon, 20 Aug 2012 12:00:26 +0200 Subject: FM Radio: Add support for FM Radio in Android Creating interface and framework for using FM Radio RX and TX from different vendors. Change-Id: I1a71aed01bfffdddfabf1cdfbfa3707cb1ed016b Signed-off-by: Benn Porscke --- include/media/stagefright/FMRadioSource.h | 64 ++++++ media/libstagefright/Android.mk | 6 + media/libstagefright/AwesomePlayer.cpp | 10 + media/libstagefright/FMRadioSource.cpp | 201 ++++++++++++++++++ media/libstagefright/MediaExtractor.cpp | 5 + media/libstagefright/PCMExtractor.cpp | 302 ++++++++++++++++++++++++++++ media/libstagefright/include/PCMExtractor.h | 61 ++++++ 7 files changed, 649 insertions(+) create mode 100644 include/media/stagefright/FMRadioSource.h create mode 100644 media/libstagefright/FMRadioSource.cpp create mode 100644 media/libstagefright/PCMExtractor.cpp create mode 100644 media/libstagefright/include/PCMExtractor.h diff --git a/include/media/stagefright/FMRadioSource.h b/include/media/stagefright/FMRadioSource.h new file mode 100644 index 0000000..32db156 --- /dev/null +++ b/include/media/stagefright/FMRadioSource.h @@ -0,0 +1,64 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * Copyright (C) 2012 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. + * + * Author: Stefan Ekenberg (stefan.ekenberg@stericsson.com) for ST-Ericsson + */ + +#ifndef FMRADIO_SOURCE_H_ + +#define FMRADIO_SOURCE_H_ + +#include +#include +#include +#include + +namespace android { + +class FMRadioSource : public DataSource { +public: + FMRadioSource(); + + virtual status_t initCheck() const; + virtual ssize_t readAt(off64_t offset, void *data, size_t size); + virtual status_t getSize(off64_t *size); + +protected: + virtual ~FMRadioSource(); + +private: + struct Buffer { + size_t frameCount; + size_t size; + int8_t* data; + }; + + status_t openRecord(int frameCount, audio_io_handle_t input); + status_t obtainBuffer(Buffer* audioBuffer); + + status_t mInitCheck; + bool mStarted; + int mSessionId; + sp mAudioRecord; + sp mCblkMemory; + audio_track_cblk_t* mCblk; + + DISALLOW_EVIL_CONSTRUCTORS(FMRadioSource); +}; + +} // namespace android + +#endif // FMRADIO_SOURCE_H_ diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 7939be4..eb89cfe 100755 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -77,6 +77,12 @@ else LOCAL_C_INCLUDES += $(TOP)/frameworks/native/include/media/openmax endif +ifeq ($(BOARD_USES_STE_FMRADIO),true) +LOCAL_SRC_FILES += \ + FMRadioSource.cpp \ + PCMExtractor.cpp +endif + ifeq ($(BOARD_USES_QCOM_HARDWARE),true) LOCAL_SRC_FILES += \ ExtendedWriter.cpp \ diff --git a/media/libstagefright/AwesomePlayer.cpp b/media/libstagefright/AwesomePlayer.cpp index 83c480d..97ee08a 100644 --- a/media/libstagefright/AwesomePlayer.cpp +++ b/media/libstagefright/AwesomePlayer.cpp @@ -50,6 +50,7 @@ #endif #include #include +#include #include #include #include @@ -2411,6 +2412,15 @@ status_t AwesomePlayer::finishSetDataSource_l() { return UNKNOWN_ERROR; } } +#ifdef STE_FM + } else if (!strncasecmp("fmradio://rx", mUri.string(), 12)) { + sniffedMIME = MEDIA_MIMETYPE_AUDIO_RAW; + dataSource = new FMRadioSource(); + status_t err = dataSource->initCheck(); + if (err != OK) { + return err; + } +#endif } else { dataSource = DataSource::CreateFromURI(mUri.string(), &mUriHeaders); } diff --git a/media/libstagefright/FMRadioSource.cpp b/media/libstagefright/FMRadioSource.cpp new file mode 100644 index 0000000..4229f23 --- /dev/null +++ b/media/libstagefright/FMRadioSource.cpp @@ -0,0 +1,201 @@ +/* + * Copyright (C) ST-Ericsson SA 2012 + * Copyright (C) 2012 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. + * + * Author: Stefan Ekenberg (stefan.ekenberg@stericsson.com) for ST-Ericsson + */ + +#define LOG_TAG "FMRadioSource" +#include + +#include +#include +#include +#include + +namespace android { + +static const int kSampleRate = 48000; +static const audio_format_t kAudioFormat = AUDIO_FORMAT_PCM_16_BIT; +static const audio_channel_mask_t kChannelMask = AUDIO_CHANNEL_IN_STEREO; +static const int kBufferTimeoutMs = 3000; + +FMRadioSource::FMRadioSource() + : mInitCheck(NO_INIT), + mStarted(false), + mSessionId(AudioSystem::newAudioSessionId()) { + + // get FM Radio RX input + audio_io_handle_t input = AudioSystem::getInput(AUDIO_SOURCE_FM_RADIO_RX, + kSampleRate, + kAudioFormat, + kChannelMask, + mSessionId); + if (input == 0) { + ALOGE("Could not get audio input for FM Radio source"); + mInitCheck = UNKNOWN_ERROR; + return; + } + + // get frame count + int frameCount = 0; + status_t status = AudioRecord::getMinFrameCount(&frameCount, kSampleRate, + kAudioFormat, popcount(kChannelMask)); + if (status != NO_ERROR) { + mInitCheck = status; + return; + } + + // create the IAudioRecord + status = openRecord(frameCount, input); + if (status != NO_ERROR) { + mInitCheck = status; + return; + } + + AudioSystem::acquireAudioSessionId(mSessionId); + + mInitCheck = OK; + return; +} + +FMRadioSource::~FMRadioSource() { + AudioSystem::releaseAudioSessionId(mSessionId); +} + +status_t FMRadioSource::initCheck() const { + return mInitCheck; +} + +ssize_t FMRadioSource::readAt(off64_t offset, void *data, size_t size) { + Buffer audioBuffer; + + if (!mStarted) { + status_t err = mAudioRecord->start(AudioSystem::SYNC_EVENT_NONE, 0); + if (err == OK) { + mStarted = true; + } else { + ALOGE("Failed to start audio source"); + return 0; + } + } + + // acquire a strong reference on the IAudioRecord and IMemory so that they cannot be destroyed + // while we are accessing the cblk + sp audioRecord = mAudioRecord; + sp iMem = mCblkMemory; + audio_track_cblk_t* cblk = mCblk; + + audioBuffer.frameCount = size / cblk->frameSize; + + status_t err = obtainBuffer(&audioBuffer); + if (err != NO_ERROR) { + ALOGE("Error obtaining an audio buffer, giving up (err:%d).", err); + return 0; + } + + memcpy(data, audioBuffer.data, audioBuffer.size); + mCblk->stepUser(audioBuffer.frameCount); + + return audioBuffer.size; +} + +status_t FMRadioSource::getSize(off64_t *size) { + *size = 0; + return OK; +} + +// ------------------------------------------------------------------------- + +status_t FMRadioSource::openRecord(int frameCount, audio_io_handle_t input) +{ + status_t status; + const sp& audioFlinger = AudioSystem::get_audio_flinger(); + if (audioFlinger == 0) { + return NO_INIT; + } + + sp record = audioFlinger->openRecord(getpid(), input, + kSampleRate, + kAudioFormat, + kChannelMask, + frameCount, + IAudioFlinger::TRACK_DEFAULT, + gettid(), + &mSessionId, + &status); + + if (record == 0) { + ALOGE("AudioFlinger could not create record track, status: %d", status); + return status; + } + + sp cblk = record->getCblk(); + if (cblk == 0) { + ALOGE("Could not get control block"); + return NO_INIT; + } + mAudioRecord = record; + mCblkMemory = cblk; + mCblk = static_cast(cblk->pointer()); + mCblk->buffers = (char*)mCblk + sizeof(audio_track_cblk_t); + android_atomic_and(~CBLK_DIRECTION_MSK, &mCblk->flags); + return NO_ERROR; +} + +status_t FMRadioSource::obtainBuffer(Buffer* audioBuffer) +{ + status_t result = NO_ERROR; + uint32_t framesReq = audioBuffer->frameCount; + + audioBuffer->frameCount = 0; + audioBuffer->size = 0; + + mCblk->lock.lock(); + uint32_t framesReady = mCblk->framesReady(); + if (framesReady == 0) { + do { + result = mCblk->cv.waitRelative(mCblk->lock, milliseconds(kBufferTimeoutMs)); + if (CC_UNLIKELY(result != NO_ERROR)) { + ALOGE("obtainBuffer timed out (is the CPU pegged?) " + "user=%08x, server=%08x", mCblk->user, mCblk->server); + mCblk->lock.unlock(); + return TIMED_OUT; + } + + framesReady = mCblk->framesReady(); + } while (framesReady == 0); + } + mCblk->lock.unlock(); + + if (framesReq > framesReady) { + framesReq = framesReady; + } + + uint32_t u = mCblk->user; + uint32_t bufferEnd = mCblk->userBase + mCblk->frameCount; + + if (framesReq > bufferEnd - u) { + framesReq = bufferEnd - u; + } + + audioBuffer->frameCount = framesReq; + audioBuffer->size = framesReq * mCblk->frameSize; + audioBuffer->data = (int8_t*)mCblk->buffer(u); + + return NO_ERROR; +} + +} // namespace android diff --git a/media/libstagefright/MediaExtractor.cpp b/media/libstagefright/MediaExtractor.cpp index 833084a..218448b 100644 --- a/media/libstagefright/MediaExtractor.cpp +++ b/media/libstagefright/MediaExtractor.cpp @@ -25,6 +25,7 @@ #include "include/FragmentedMP4Extractor.h" #include "include/WAVExtractor.h" #include "include/OggExtractor.h" +#include "include/PCMExtractor.h" #include "include/MPEG2PSExtractor.h" #include "include/MPEG2TSExtractor.h" #include "include/DRMExtractor.h" @@ -136,6 +137,10 @@ sp MediaExtractor::Create( ret = new AACExtractor(source, meta); } else if (!strcasecmp(mime, MEDIA_MIMETYPE_CONTAINER_MPEG2PS)) { ret = new MPEG2PSExtractor(source); +#ifdef STE_FM + } else if (!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_RAW)) { + ret = new PCMExtractor(source); +#endif } if (ret != NULL) { diff --git a/media/libstagefright/PCMExtractor.cpp b/media/libstagefright/PCMExtractor.cpp new file mode 100644 index 0000000..bb26bcd --- /dev/null +++ b/media/libstagefright/PCMExtractor.cpp @@ -0,0 +1,302 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: Andreas Gustafsson (andreas.a.gustafsson@stericsson.com) + * for ST-Ericsson + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "PCMExtractor" +#include + +#include "include/PCMExtractor.h" + +#include +#include +#include +#include +#include + +namespace android { + +/** +* The default buffer size. +*/ +static const uint16_t kDefaultNumChannels = 2; + +/** +* The default Sample rate. +*/ +static const uint32_t kDefaultSampleRate = 48000; + +/** +* Bits per sample. +*/ +static const uint16_t kDefaultBitsPerSample = 16; + +/** +* The default buffer size. +*/ +static const uint32_t kDefaultBufferSize = 4800; + +/** +* Buffer duration in ms, to be used for input +*/ +static const uint16_t kInputBufferDuration = 64; + +/** +* Buffer granulairity in samples to be used for input. +*/ +static const uint16_t kBufferGranularityInSamples = 16; + +struct PCMSource : public MediaSource { + PCMSource( + const sp &dataSource, + const sp &meta, + int32_t bitsPerSample, + off_t offset, size_t size); + + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + virtual sp getFormat(); + + virtual status_t read( + MediaBuffer **buffer, const ReadOptions *options = NULL); + +protected: + virtual ~PCMSource(); + +private: + static const size_t kMaxFrameSize; + + sp mDataSource; + sp mMeta; + int32_t mSampleRate; + int32_t mNumChannels; + int32_t mBitsPerSample; + off_t mOffset; + size_t mSize; + bool mStarted; + MediaBufferGroup *mGroup; + off_t mCurrentPos; + uint32_t mBufferSize; + + DISALLOW_EVIL_CONSTRUCTORS(PCMSource); +}; + +PCMExtractor::PCMExtractor(const sp &source) + : mDataSource(source), + mValidFormat(false) { + mInitCheck = init(); +} + +PCMExtractor::~PCMExtractor() { +} + +sp PCMExtractor::getMetaData() { + sp meta = new MetaData; + + if (mInitCheck != OK) { + return meta; + } + + meta->setCString(kKeyMIMEType, "audio/raw"); + + return meta; +} + +size_t PCMExtractor::countTracks() { + return mInitCheck == OK ? 1 : 0; +} + +sp PCMExtractor::getTrack(size_t index) { + if (mInitCheck != OK || index > 0) { + return NULL; + } + + return new PCMSource( + mDataSource, mTrackMeta, + kDefaultBitsPerSample, mDataOffset, mDataSize); +} + +sp PCMExtractor::getTrackMetaData( + size_t index, uint32_t flags) { + if (mInitCheck != OK || index > 0) { + return NULL; + } + + return mTrackMeta; +} + +status_t PCMExtractor::init() { + mDataOffset = 0; + mDataSize = 0; + mValidFormat = true; + mTrackMeta = new MetaData;mTrackMeta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW); + mTrackMeta->setInt32(kKeyChannelCount, kDefaultNumChannels); + mTrackMeta->setInt32(kKeySampleRate, kDefaultSampleRate); + return OK; +} + +const size_t PCMSource::kMaxFrameSize = 4800; + +PCMSource::PCMSource( + const sp &dataSource, + const sp &meta, + int32_t bitsPerSample, + off_t offset, size_t size) + : mDataSource(dataSource), + mMeta(meta), + mSampleRate(0), + mNumChannels(0), + mBitsPerSample(bitsPerSample), + mOffset(offset), + mSize(size), + mStarted(false), + mGroup(NULL), + mBufferSize(0) { + CHECK(mMeta->findInt32(kKeySampleRate, &mSampleRate)); + CHECK(mMeta->findInt32(kKeyChannelCount, &mNumChannels)); +} + +PCMSource::~PCMSource() { + if (mStarted) { + stop(); + } +} + +status_t PCMSource::start(MetaData *params) { + CHECK(!mStarted); + + size_t size = kDefaultBufferSize; + + if (mSampleRate != 0 && mNumChannels != 0) { + mBufferSize = mSampleRate * kInputBufferDuration / 1000 * mNumChannels * 2; + size_t granularity = kBufferGranularityInSamples * 2 * mNumChannels; + mBufferSize = (mBufferSize / granularity) * granularity; + } + mGroup = new MediaBufferGroup; + mGroup->add_buffer(new MediaBuffer(mBufferSize)); + + if (mBitsPerSample == 8) { + // As a temporary buffer for 8->16 bit conversion. + mGroup->add_buffer(new MediaBuffer(mBufferSize)); + } + + mCurrentPos = mOffset; + + mStarted = true; + return OK; +} + +status_t PCMSource::stop() { + + CHECK(mStarted); + delete mGroup; + mGroup = NULL; + + mStarted = false; + return OK; +} + +sp PCMSource::getFormat() { + return mMeta; +} + +status_t PCMSource::read( + MediaBuffer **out, const ReadOptions *options) { + *out = NULL; + int64_t seekTimeUs; + ReadOptions::SeekMode seek = ReadOptions::SEEK_CLOSEST_SYNC; + if (options != NULL && options->getSeekTo(&seekTimeUs,&seek)) { + int64_t pos = (seekTimeUs * mSampleRate) / 1000000 * mNumChannels * 2; + if (pos > mSize) { + pos = mSize; + } + mCurrentPos = pos + mOffset; + } + + MediaBuffer *buffer; + status_t err = mGroup->acquire_buffer(&buffer); + if (err != OK) { + return err; + } + + ssize_t n = mDataSource->readAt( + mCurrentPos, buffer->data(), mBufferSize); + if (n <= 0) { + buffer->release(); + buffer = NULL; + return ERROR_END_OF_STREAM; + } + + mCurrentPos += n; + + buffer->set_range(0, n); + + if (mBitsPerSample == 8) { + // Convert 8-bit unsigned samples to 16-bit signed. + + MediaBuffer *tmp; + CHECK_EQ(mGroup->acquire_buffer(&tmp), (status_t)OK); + + // The new buffer holds the sample number of samples, but each + // one is 2 bytes wide. + tmp->set_range(0, 2 * n); + + int16_t *dst = (int16_t *)tmp->data(); + const uint8_t *src = (const uint8_t *)buffer->data(); + while (n-- > 0) { + *dst++ = ((int16_t)(*src) - 128) * 256; + ++src; + } + + buffer->release(); + buffer = tmp; + } else if (mBitsPerSample == 24) { + // Convert 24-bit signed samples to 16-bit signed. + + const uint8_t *src = + (const uint8_t *)buffer->data() + buffer->range_offset(); + int16_t *dst = (int16_t *)src; + + size_t numSamples = buffer->range_length() / 3; + for (size_t i = 0; i < numSamples; ++i) { + int32_t x = (int32_t)(src[0] | src[1] << 8 | src[2] << 16); + x = (x << 8) >> 8; // sign extension + + x = x >> 8; + *dst++ = (int16_t)x; + src += 3; + } + + buffer->set_range(buffer->range_offset(), 2 * numSamples); + } + + size_t bytesPerSample = mBitsPerSample >> 3; + + buffer->meta_data()->setInt64( + kKeyTime, + 1000000LL * (mCurrentPos - mOffset) + / (mNumChannels * bytesPerSample) / mSampleRate); + + + *out = buffer; + + return OK; +} + +} // namespace android diff --git a/media/libstagefright/include/PCMExtractor.h b/media/libstagefright/include/PCMExtractor.h new file mode 100644 index 0000000..4717d10 --- /dev/null +++ b/media/libstagefright/include/PCMExtractor.h @@ -0,0 +1,61 @@ +/* + * Copyright (C) ST-Ericsson SA 2010 + * Copyright (C) 2010 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + * + * Author: Andreas Gustafsson (andreas.a.gustafsson@stericsson.com) + * for ST-Ericsson + */ + +#ifndef PCM_EXTRACTOR_H_ + +#define PCM_EXTRACTOR_H_ + +#include +#include +#include +#include + +namespace android { + +class PCMExtractor : public MediaExtractor { +public: + // Extractor assumes ownership of "source". + PCMExtractor(const sp &source); + + virtual size_t countTracks(); + virtual sp getTrack(size_t index); + virtual sp getTrackMetaData(size_t index, uint32_t flags); + + virtual sp getMetaData(); + +protected: + virtual ~PCMExtractor(); + +private: + sp mDataSource; + status_t mInitCheck; + bool mValidFormat; + off_t mDataOffset; + size_t mDataSize; + sp mTrackMeta; + + status_t init(); + + DISALLOW_EVIL_CONSTRUCTORS(PCMExtractor); +}; + +} // namespace android + +#endif // PCM_EXTRACTOR_H_ -- cgit v1.1