blob: 8f1fa67e77810f387b7b8eb998519fef7dff427e [file] [log] [blame]
/*
* 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