blob: c8dfeded7fc5b1190ffcae16b61ba7546df4d9c6 [file] [log] [blame]
/*
* Copyright (C) 2010 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
//#define LOG_NDEBUG 0
#define LOG_TAG "AudioSource"
#include <utils/Log.h>
#include <media/stagefright/AudioSource.h>
#include <media/AudioRecord.h>
#include <media/stagefright/MediaBufferGroup.h>
#include <media/stagefright/MediaDebug.h>
#include <media/stagefright/MediaDefs.h>
#include <media/stagefright/MetaData.h>
#include <cutils/properties.h>
#include <stdlib.h>
namespace android {
AudioSource::AudioSource(
int inputSource, uint32_t sampleRate, uint32_t channels)
: mStarted(false),
mCollectStats(false),
mPrevSampleTimeUs(0),
mTotalLostFrames(0),
mPrevLostBytes(0),
mGroup(NULL) {
LOGV("sampleRate: %d, channels: %d", sampleRate, channels);
uint32_t flags = AudioRecord::RECORD_AGC_ENABLE |
AudioRecord::RECORD_NS_ENABLE |
AudioRecord::RECORD_IIR_ENABLE;
mRecord = new AudioRecord(
inputSource, sampleRate, AudioSystem::PCM_16_BIT,
channels > 1? AudioSystem::CHANNEL_IN_STEREO: AudioSystem::CHANNEL_IN_MONO,
4 * kMaxBufferSize / sizeof(int16_t), /* Enable ping-pong buffers */
flags);
mInitCheck = mRecord->initCheck();
}
AudioSource::~AudioSource() {
if (mStarted) {
stop();
}
delete mRecord;
mRecord = NULL;
}
status_t AudioSource::initCheck() const {
return mInitCheck;
}
status_t AudioSource::start(MetaData *params) {
if (mStarted) {
return UNKNOWN_ERROR;
}
char value[PROPERTY_VALUE_MAX];
if (property_get("media.stagefright.record-stats", value, NULL)
&& (!strcmp(value, "1") || !strcasecmp(value, "true"))) {
mCollectStats = true;
}
mTrackMaxAmplitude = false;
mMaxAmplitude = 0;
mStartTimeUs = 0;
int64_t startTimeUs;
if (params && params->findInt64(kKeyTime, &startTimeUs)) {
mStartTimeUs = startTimeUs;
}
status_t err = mRecord->start();
if (err == OK) {
mGroup = new MediaBufferGroup;
mGroup->add_buffer(new MediaBuffer(kMaxBufferSize));
mStarted = true;
}
return err;
}
status_t AudioSource::stop() {
if (!mStarted) {
return UNKNOWN_ERROR;
}
mRecord->stop();
delete mGroup;
mGroup = NULL;
mStarted = false;
if (mCollectStats) {
LOGI("Total lost audio frames: %lld",
mTotalLostFrames + (mPrevLostBytes >> 1));
}
return OK;
}
sp<MetaData> AudioSource::getFormat() {
sp<MetaData> meta = new MetaData;
meta->setCString(kKeyMIMEType, MEDIA_MIMETYPE_AUDIO_RAW);
meta->setInt32(kKeySampleRate, mRecord->getSampleRate());
meta->setInt32(kKeyChannelCount, mRecord->channelCount());
meta->setInt32(kKeyMaxInputSize, kMaxBufferSize);
return meta;
}
/*
* Returns -1 if frame skipping request is too long.
* Returns 0 if there is no need to skip frames.
* Returns 1 if we need to skip frames.
*/
static int skipFrame(int64_t timestampUs,
const MediaSource::ReadOptions *options) {
int64_t skipFrameUs;
if (!options || !options->getSkipFrame(&skipFrameUs)) {
return 0;
}
if (skipFrameUs <= timestampUs) {
return 0;
}
// Safe guard against the abuse of the kSkipFrame_Option.
if (skipFrameUs - timestampUs >= 1E6) {
LOGE("Frame skipping requested is way too long: %lld us",
skipFrameUs - timestampUs);
return -1;
}
LOGV("skipFrame: %lld us > timestamp: %lld us",
skipFrameUs, timestampUs);
return 1;
}
status_t AudioSource::read(
MediaBuffer **out, const ReadOptions *options) {
*out = NULL;
MediaBuffer *buffer;
CHECK_EQ(mGroup->acquire_buffer(&buffer), OK);
int err = 0;
while (mStarted) {
uint32_t numFramesRecorded;
mRecord->getPosition(&numFramesRecorded);
if (numFramesRecorded == 0 && mPrevSampleTimeUs == 0) {
// Initial delay
if (mStartTimeUs > 0) {
mStartTimeUs = systemTime() / 1000 - mStartTimeUs;
} else {
// Assume latency is constant.
mStartTimeUs += mRecord->latency() * 1000;
}
mPrevSampleTimeUs = mStartTimeUs;
}
uint32_t sampleRate = mRecord->getSampleRate();
// Insert null frames when lost frames are detected.
int64_t timestampUs = mPrevSampleTimeUs;
uint32_t numLostBytes = mRecord->getInputFramesLost() << 1;
numLostBytes += mPrevLostBytes;
#if 0
// Simulate lost frames
numLostBytes = ((rand() * 1.0 / RAND_MAX)) * 2 * kMaxBufferSize;
numLostBytes &= 0xFFFFFFFE; // Alignment requirement
// Reduce the chance to lose
if (rand() * 1.0 / RAND_MAX >= 0.05) {
numLostBytes = 0;
}
#endif
if (numLostBytes > 0) {
if (numLostBytes > kMaxBufferSize) {
mPrevLostBytes = numLostBytes - kMaxBufferSize;
numLostBytes = kMaxBufferSize;
}
CHECK_EQ(numLostBytes & 1, 0);
timestampUs += ((1000000LL * (numLostBytes >> 1)) +
(sampleRate >> 1)) / sampleRate;
CHECK(timestampUs > mPrevSampleTimeUs);
if (mCollectStats) {
mTotalLostFrames += (numLostBytes >> 1);
}
if ((err = skipFrame(timestampUs, options)) == -1) {
buffer->release();
return UNKNOWN_ERROR;
} else if (err != 0) {
continue;
}
memset(buffer->data(), 0, numLostBytes);
buffer->set_range(0, numLostBytes);
buffer->meta_data()->setInt64(kKeyTime, mPrevSampleTimeUs);
mPrevSampleTimeUs = timestampUs;
*out = buffer;
return OK;
}
ssize_t n = mRecord->read(buffer->data(), buffer->size());
if (n < 0) {
buffer->release();
return (status_t)n;
}
int64_t recordDurationUs = (1000000LL * n >> 1) / sampleRate;
timestampUs += recordDurationUs;
if ((err = skipFrame(timestampUs, options)) == -1) {
buffer->release();
return UNKNOWN_ERROR;
} else if (err != 0) {
continue;
}
if (mTrackMaxAmplitude) {
trackMaxAmplitude((int16_t *) buffer->data(), n >> 1);
}
buffer->meta_data()->setInt64(kKeyTime, mPrevSampleTimeUs);
CHECK(timestampUs > mPrevSampleTimeUs);
if (mTotalLostFrames == 0) {
CHECK_EQ(mPrevSampleTimeUs,
mStartTimeUs + (1000000LL * numFramesRecorded) / sampleRate);
}
mPrevSampleTimeUs = timestampUs;
LOGV("initial delay: %lld, sample rate: %d, timestamp: %lld",
mStartTimeUs, sampleRate, timestampUs);
buffer->set_range(0, n);
*out = buffer;
return OK;
}
return OK;
}
void AudioSource::trackMaxAmplitude(int16_t *data, int nSamples) {
for (int i = nSamples; i > 0; --i) {
int16_t value = *data++;
if (value < 0) {
value = -value;
}
if (mMaxAmplitude < value) {
mMaxAmplitude = value;
}
}
}
int16_t AudioSource::getMaxAmplitude() {
// First call activates the tracking.
if (!mTrackMaxAmplitude) {
mTrackMaxAmplitude = true;
}
int16_t value = mMaxAmplitude;
mMaxAmplitude = 0;
LOGV("max amplitude since last call: %d", value);
return value;
}
} // namespace android