From f84bfabe0d5d92956a1ba2896beb0e0d4e1e3106 Mon Sep 17 00:00:00 2001 From: James Dong Date: Mon, 21 Mar 2011 14:29:38 -0700 Subject: Initial check-in for AACWriter Change-Id: Ia21ca39a404484b2dda25c6101780d2ff11c4623 related-to-bug: 4211046 --- include/media/stagefright/AACWriter.h | 75 ++++ .../libmediaplayerservice/StagefrightRecorder.cpp | 27 +- media/libmediaplayerservice/StagefrightRecorder.h | 3 +- media/libstagefright/AACWriter.cpp | 382 +++++++++++++++++++++ media/libstagefright/Android.mk | 1 + media/libstagefright/codecs/aacenc/AACEncoder.cpp | 2 +- 6 files changed, 483 insertions(+), 7 deletions(-) create mode 100644 include/media/stagefright/AACWriter.h create mode 100644 media/libstagefright/AACWriter.cpp diff --git a/include/media/stagefright/AACWriter.h b/include/media/stagefright/AACWriter.h new file mode 100644 index 0000000..fa3ab8a --- /dev/null +++ b/include/media/stagefright/AACWriter.h @@ -0,0 +1,75 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +#ifndef AAC_WRITER_H_ +#define AAC_WRITER_H_ + +#include +#include + +namespace android { + +struct MediaSource; +struct MetaData; + +struct AACWriter : public MediaWriter { + AACWriter(const char *filename); + AACWriter(int fd); + + status_t initCheck() const; + + virtual status_t addSource(const sp &source); + virtual bool reachedEOS(); + virtual status_t start(MetaData *params = NULL); + virtual status_t stop(); + virtual status_t pause(); + +protected: + virtual ~AACWriter(); + +private: + enum { + kAdtsHeaderLength = 7, // # of bytes for the adts header + kSamplesPerFrame = 1024, // # of samples in a frame + }; + + int mFd; + status_t mInitCheck; + sp mSource; + bool mStarted; + volatile bool mPaused; + volatile bool mResumed; + volatile bool mDone; + volatile bool mReachedEOS; + pthread_t mThread; + int64_t mEstimatedSizeBytes; + int64_t mEstimatedDurationUs; + int32_t mChannelCount; + int32_t mSampleRate; + int32_t mFrameDurationUs; + + static void *ThreadWrapper(void *); + status_t threadFunc(); + bool exceedsFileSizeLimit(); + bool exceedsFileDurationLimit(); + status_t writeAdtsHeader(uint32_t frameLength); + + DISALLOW_EVIL_CONSTRUCTORS(AACWriter); +}; + +} // namespace android + +#endif // AAC_WRITER_H_ diff --git a/media/libmediaplayerservice/StagefrightRecorder.cpp b/media/libmediaplayerservice/StagefrightRecorder.cpp index 01fbea1..c1687c4 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.cpp +++ b/media/libmediaplayerservice/StagefrightRecorder.cpp @@ -26,6 +26,7 @@ #include #include #include +#include #include #include #include @@ -872,15 +873,21 @@ sp StagefrightRecorder::createAudioSource() { } status_t StagefrightRecorder::startAACRecording() { - CHECK(mOutputFormat == OUTPUT_FORMAT_AAC_ADIF || - mOutputFormat == OUTPUT_FORMAT_AAC_ADTS); + // FIXME: + // Add support for OUTPUT_FORMAT_AAC_ADIF + CHECK(mOutputFormat == OUTPUT_FORMAT_AAC_ADTS); CHECK(mAudioEncoder == AUDIO_ENCODER_AAC); CHECK(mAudioSource != AUDIO_SOURCE_CNT); - CHECK(0 == "AACWriter is not implemented yet"); + mWriter = new AACWriter(mOutputFd); + status_t status = startRawAudioRecording(); + if (status != OK) { + mWriter.clear(); + mWriter = NULL; + } - return OK; + return status; } status_t StagefrightRecorder::startAMRRecording() { @@ -902,6 +909,16 @@ status_t StagefrightRecorder::startAMRRecording() { } } + mWriter = new AMRWriter(mOutputFd); + status_t status = startRawAudioRecording(); + if (status != OK) { + mWriter.clear(); + mWriter = NULL; + } + return status; +} + +status_t StagefrightRecorder::startRawAudioRecording() { if (mAudioSource >= AUDIO_SOURCE_CNT) { LOGE("Invalid audio source: %d", mAudioSource); return BAD_VALUE; @@ -917,7 +934,7 @@ status_t StagefrightRecorder::startAMRRecording() { return UNKNOWN_ERROR; } - mWriter = new AMRWriter(mOutputFd); + CHECK(mWriter != 0); mWriter->addSource(audioEncoder); if (mMaxFileDurationUs != 0) { diff --git a/media/libmediaplayerservice/StagefrightRecorder.h b/media/libmediaplayerservice/StagefrightRecorder.h index 3d463ea..c3e6d65 100644 --- a/media/libmediaplayerservice/StagefrightRecorder.h +++ b/media/libmediaplayerservice/StagefrightRecorder.h @@ -69,6 +69,7 @@ private: sp mPreviewSurface; sp mListener; sp mWriter, mWriterAux; + int mOutputFd, mOutputFdAux; sp mAudioSourceNode; audio_source_t mAudioSource; @@ -104,7 +105,6 @@ private: sp mCameraSourceTimeLapse; String8 mParams; - int mOutputFd, mOutputFdAux; bool mIsMetaDataStoredInVideoBuffers; MediaProfiles *mEncoderProfiles; @@ -123,6 +123,7 @@ private: status_t startMPEG4Recording(); status_t startAMRRecording(); status_t startAACRecording(); + status_t startRawAudioRecording(); status_t startRTPRecording(); status_t startMPEG2TSRecording(); sp createAudioSource(); diff --git a/media/libstagefright/AACWriter.cpp b/media/libstagefright/AACWriter.cpp new file mode 100644 index 0000000..8413208 --- /dev/null +++ b/media/libstagefright/AACWriter.cpp @@ -0,0 +1,382 @@ +/* + * Copyright (C) 2011 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +//#define LOG_NDEBUG 0 +#define LOG_TAG "AACWriter" +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +namespace android { + +AACWriter::AACWriter(const char *filename) + : mFd(-1), + mInitCheck(NO_INIT), + mStarted(false), + mPaused(false), + mResumed(false), + mChannelCount(-1), + mSampleRate(-1) { + + LOGV("AACWriter Constructor"); + + mFd = open(filename, O_CREAT | O_LARGEFILE | O_TRUNC | O_RDWR); + if (mFd >= 0) { + mInitCheck = OK; + } +} + +AACWriter::AACWriter(int fd) + : mFd(dup(fd)), + mInitCheck(mFd < 0? NO_INIT: OK), + mStarted(false), + mPaused(false), + mResumed(false), + mChannelCount(-1), + mSampleRate(-1) { +} + +AACWriter::~AACWriter() { + if (mStarted) { + stop(); + } + + if (mFd != -1) { + close(mFd); + mFd = -1; + } +} + +status_t AACWriter::initCheck() const { + return mInitCheck; +} + +static int writeInt8(int fd, uint8_t x) { + return ::write(fd, &x, 1); +} + + +status_t AACWriter::addSource(const sp &source) { + if (mInitCheck != OK) { + return mInitCheck; + } + + if (mSource != NULL) { + LOGE("AAC files only support a single track of audio."); + return UNKNOWN_ERROR; + } + + sp meta = source->getFormat(); + + const char *mime; + CHECK(meta->findCString(kKeyMIMEType, &mime)); + + CHECK(!strcasecmp(mime, MEDIA_MIMETYPE_AUDIO_AAC)); + CHECK(meta->findInt32(kKeyChannelCount, &mChannelCount)); + CHECK(meta->findInt32(kKeySampleRate, &mSampleRate)); + CHECK(mChannelCount >= 1 && mChannelCount <= 2); + + mSource = source; + return OK; +} + +status_t AACWriter::start(MetaData *params) { + if (mInitCheck != OK) { + return mInitCheck; + } + + if (mSource == NULL) { + return UNKNOWN_ERROR; + } + + if (mStarted && mPaused) { + mPaused = false; + mResumed = true; + return OK; + } else if (mStarted) { + // Already started, does nothing + return OK; + } + + mFrameDurationUs = (kSamplesPerFrame * 1000000LL + (mSampleRate >> 1)) + / mSampleRate; + + status_t err = mSource->start(); + + if (err != OK) { + return err; + } + + pthread_attr_t attr; + pthread_attr_init(&attr); + pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_JOINABLE); + + mReachedEOS = false; + mDone = false; + + pthread_create(&mThread, &attr, ThreadWrapper, this); + pthread_attr_destroy(&attr); + + mStarted = true; + + return OK; +} + +status_t AACWriter::pause() { + if (!mStarted) { + return OK; + } + mPaused = true; + return OK; +} + +status_t AACWriter::stop() { + if (!mStarted) { + return OK; + } + + mDone = true; + + void *dummy; + pthread_join(mThread, &dummy); + + status_t err = (status_t) dummy; + { + status_t status = mSource->stop(); + if (err == OK && + (status != OK && status != ERROR_END_OF_STREAM)) { + err = status; + } + } + + mStarted = false; + return err; +} + +bool AACWriter::exceedsFileSizeLimit() { + if (mMaxFileSizeLimitBytes == 0) { + return false; + } + return mEstimatedSizeBytes >= mMaxFileSizeLimitBytes; +} + +bool AACWriter::exceedsFileDurationLimit() { + if (mMaxFileDurationLimitUs == 0) { + return false; + } + return mEstimatedDurationUs >= mMaxFileDurationLimitUs; +} + +// static +void *AACWriter::ThreadWrapper(void *me) { + return (void *) static_cast(me)->threadFunc(); +} + +/* +* Returns an index into the sample rate table if the +* given sample rate is found; otherwise, returns -1. +*/ +static bool getSampleRateTableIndex(int sampleRate, uint8_t* tableIndex) { + static const int kSampleRateTable[] = { + 96000, 88200, 64000, 48000, 44100, 32000, + 24000, 22050, 16000, 12000, 11025, 8000 + }; + const int tableSize = + sizeof(kSampleRateTable) / sizeof(kSampleRateTable[0]); + + *tableIndex = 0; + for (int index = 0; index < tableSize; ++index) { + if (sampleRate == kSampleRateTable[index]) { + LOGV("Sample rate: %d and index: %d", + sampleRate, index); + *tableIndex = index; + return true; + } + } + + LOGE("Sampling rate %d bps is not supported", sampleRate); + return false; +} + +/* + * ADTS (Audio data transport stream) header structure. + * It consists of 7 or 9 bytes (with or without CRC): + * 12 bits of syncword 0xFFF, all bits must be 1 + * 1 bit of field ID. 0 for MPEG-4, and 1 for MPEG-2 + * 2 bits of MPEG layer. If in MPEG-TS, set to 0 + * 1 bit of protection absense. Set to 1 if no CRC. + * 2 bits of profile code. Set to 1 (The MPEG-4 Audio + * object type minus 1. We are using AAC-LC = 2) + * 4 bits of sampling frequency index code (15 is not allowed) + * 1 bit of private stream. Set to 0. + * 3 bits of channel configuration code. 0 resevered for inband PCM + * 1 bit of originality. Set to 0. + * 1 bit of home. Set to 0. + * 1 bit of copyrighted steam. Set to 0. + * 1 bit of copyright start. Set to 0. + * 13 bits of frame length. It included 7 ot 9 bytes header length. + * it is set to (protection absense? 7: 9) + size(AAC frame) + * 11 bits of buffer fullness. 0x7FF for VBR. + * 2 bits of frames count in one packet. Set to 0. + */ +status_t AACWriter::writeAdtsHeader(uint32_t frameLength) { + uint8_t data = 0xFF; + write(mFd, &data, 1); + + const uint8_t kFieldId = 0; + const uint8_t kMpegLayer = 0; + const uint8_t kProtectionAbsense = 1; // 1: kAdtsHeaderLength = 7 + data = 0xF0; + data |= (kFieldId << 3); + data |= (kMpegLayer << 1); + data |= kProtectionAbsense; + write(mFd, &data, 1); + + const uint8_t kProfileCode = 1; // AAC-LC + uint8_t kSampleFreqIndex; + CHECK(getSampleRateTableIndex(mSampleRate, &kSampleFreqIndex)); + const uint8_t kPrivateStream = 0; + const uint8_t kChannelConfigCode = mChannelCount; + data = (kProfileCode << 6); + data |= (kSampleFreqIndex << 2); + data |= (kPrivateStream << 1); + data |= (kChannelConfigCode >> 2); + write(mFd, &data, 1); + + // 4 bits from originality to copyright start + const uint8_t kCopyright = 0; + const uint32_t kFrameLength = frameLength; + data = ((kChannelConfigCode & 3) << 6); + data |= (kCopyright << 2); + data |= ((kFrameLength & 0x1800) >> 11); + write(mFd, &data, 1); + + data = ((kFrameLength & 0x07F8) >> 3); + write(mFd, &data, 1); + + const uint32_t kBufferFullness = 0x7FF; // VBR + data = ((kFrameLength & 0x07) << 5); + data |= ((kBufferFullness & 0x07C0) >> 6); + write(mFd, &data, 1); + + const uint8_t kFrameCount = 0; + data = ((kBufferFullness & 0x03F) << 2); + data |= kFrameCount; + write(mFd, &data, 1); + + return OK; +} + +status_t AACWriter::threadFunc() { + mEstimatedDurationUs = 0; + mEstimatedSizeBytes = 0; + int64_t previousPausedDurationUs = 0; + int64_t maxTimestampUs = 0; + status_t err = OK; + + prctl(PR_SET_NAME, (unsigned long)"AACWriterThread", 0, 0, 0); + + while (!mDone && err == OK) { + MediaBuffer *buffer; + err = mSource->read(&buffer); + + if (err != OK) { + break; + } + + if (mPaused) { + buffer->release(); + buffer = NULL; + continue; + } + + mEstimatedSizeBytes += kAdtsHeaderLength + buffer->range_length(); + if (exceedsFileSizeLimit()) { + buffer->release(); + buffer = NULL; + notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_FILESIZE_REACHED, 0); + break; + } + + int32_t isCodecSpecific = 0; + if (buffer->meta_data()->findInt32(kKeyIsCodecConfig, &isCodecSpecific) && isCodecSpecific) { + LOGV("Drop codec specific info buffer"); + buffer->release(); + buffer = NULL; + continue; + } + + int64_t timestampUs; + CHECK(buffer->meta_data()->findInt64(kKeyTime, ×tampUs)); + if (timestampUs > mEstimatedDurationUs) { + mEstimatedDurationUs = timestampUs; + } + if (mResumed) { + previousPausedDurationUs += (timestampUs - maxTimestampUs - mFrameDurationUs); + mResumed = false; + } + timestampUs -= previousPausedDurationUs; + LOGV("time stamp: %lld, previous paused duration: %lld", + timestampUs, previousPausedDurationUs); + if (timestampUs > maxTimestampUs) { + maxTimestampUs = timestampUs; + } + + if (exceedsFileDurationLimit()) { + buffer->release(); + buffer = NULL; + notify(MEDIA_RECORDER_EVENT_INFO, MEDIA_RECORDER_INFO_MAX_DURATION_REACHED, 0); + break; + } + + // Each output AAC audio frame to the file contains + // 1. an ADTS header, followed by + // 2. the compressed audio data. + ssize_t dataLength = buffer->range_length(); + uint8_t *data = (uint8_t *)buffer->data() + buffer->range_offset(); + if (writeAdtsHeader(kAdtsHeaderLength + dataLength) != OK || + dataLength != write(mFd, data, dataLength)) { + err = ERROR_IO; + } + + buffer->release(); + buffer = NULL; + } + + close(mFd); + mFd = -1; + mReachedEOS = true; + if (err == ERROR_END_OF_STREAM) { + return OK; + } + return err; +} + +bool AACWriter::reachedEOS() { + return mReachedEOS; +} + +} // namespace android diff --git a/media/libstagefright/Android.mk b/media/libstagefright/Android.mk index 6c7176c..10ce00c 100644 --- a/media/libstagefright/Android.mk +++ b/media/libstagefright/Android.mk @@ -6,6 +6,7 @@ include frameworks/base/media/libstagefright/codecs/common/Config.mk LOCAL_SRC_FILES:= \ ACodec.cpp \ AACExtractor.cpp \ + AACWriter.cpp \ AMRExtractor.cpp \ AMRWriter.cpp \ AVIExtractor.cpp \ diff --git a/media/libstagefright/codecs/aacenc/AACEncoder.cpp b/media/libstagefright/codecs/aacenc/AACEncoder.cpp index e4ff128..0bff52d 100644 --- a/media/libstagefright/codecs/aacenc/AACEncoder.cpp +++ b/media/libstagefright/codecs/aacenc/AACEncoder.cpp @@ -84,7 +84,7 @@ status_t AACEncoder::initCheck() { params.sampleRate = mSampleRate; params.bitRate = mBitRate; params.nChannels = mChannels; - params.adtsUsed = 0; // For MP4 file, don't use adts format$ + params.adtsUsed = 0; // We add adts header in the file writer if needed. if (VO_ERR_NONE != mApiHandle->SetParam(mEncoderHandle, VO_PID_AAC_ENCPARAM, ¶ms)) { LOGE("Failed to set AAC encoder parameters"); return UNKNOWN_ERROR; -- cgit v1.1