Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit 13a5b94e authored by Linus Nilsson's avatar Linus Nilsson Committed by Android (Google) Code Review
Browse files

Merge "Added MediaSampleReader interface and an NDK based implementation."

parents 4a07dbf5 478df7e3
Loading
Loading
Loading
Loading
+31 −0
Original line number Diff line number Diff line
#!/bin/bash
#
# Script to run all transcoding related tests from subfolders.
# Run script from this folder.
#

if [ -z "$ANDROID_BUILD_TOP" ]; then
    echo "Android build environment not set"
    exit -1
fi

# ensure we have mm
. $ANDROID_BUILD_TOP/build/envsetup.sh

mm

echo "waiting for device"

adb root && adb wait-for-device remount && adb sync
SYNC_FINISHED=true

# Run the transcoding service tests.
pushd tests
. build_and_run_all_unit_tests.sh
popd

# Run the transcoder tests.
pushd transcoder/tests/
. build_and_run_all_unit_tests.sh
popd
+11 −10
Original line number Diff line number Diff line
@@ -3,6 +3,7 @@
# Run tests in this directory.
#

if [ "$SYNC_FINISHED" != true ]; then
  if [ -z "$ANDROID_BUILD_TOP" ]; then
      echo "Android build environment not set"
      exit -1
@@ -16,6 +17,7 @@ mm
  echo "waiting for device"

  adb root && adb wait-for-device remount && adb sync
fi

echo "========================================"

@@ -30,4 +32,3 @@ adb shell /data/nativetest/AdjustableMaxPriorityQueue_tests/AdjustableMaxPriorit
echo "testing TranscodingJobScheduler"
#adb shell /data/nativetest64/TranscodingJobScheduler_tests/TranscodingJobScheduler_tests
adb shell /data/nativetest/TranscodingJobScheduler_tests/TranscodingJobScheduler_tests
+48 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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.
 */

cc_library_shared {
    name: "libmediatranscoder",

    srcs: [
        "MediaSampleReaderNDK.cpp",
    ],

    shared_libs: [
        "libbase",
        "libcutils",
        "libmediandk",
        "libutils",
    ],

    export_include_dirs: [
        "include",
    ],

    cflags: [
        "-Werror",
        "-Wno-error=deprecated-declarations",
        "-Wall",
    ],

    sanitize: {
        misc_undefined: [
            "unsigned-integer-overflow",
            "signed-integer-overflow",
        ],
        cfi: true,
    },
}
+260 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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 "MediaSampleReader"

#include <android-base/logging.h>
#include <media/MediaSampleReaderNDK.h>

#include <algorithm>
#include <vector>

namespace android {

// static
std::shared_ptr<MediaSampleReader> MediaSampleReaderNDK::createFromFd(int fd, size_t offset,
                                                                      size_t size) {
    AMediaExtractor* extractor = AMediaExtractor_new();
    if (extractor == nullptr) {
        LOG(ERROR) << "Unable to allocate AMediaExtractor";
        return nullptr;
    }

    media_status_t status = AMediaExtractor_setDataSourceFd(extractor, fd, offset, size);
    if (status != AMEDIA_OK) {
        LOG(ERROR) << "AMediaExtractor_setDataSourceFd returned error: " << status;
        AMediaExtractor_delete(extractor);
        return nullptr;
    }

    auto sampleReader = std::shared_ptr<MediaSampleReaderNDK>(new MediaSampleReaderNDK(extractor));
    status = sampleReader->init();
    if (status != AMEDIA_OK) {
        LOG(ERROR) << "MediaSampleReaderNDK::init returned error: " << status;
        return nullptr;
    }

    return sampleReader;
}

MediaSampleReaderNDK::MediaSampleReaderNDK(AMediaExtractor* extractor)
      : mExtractor(extractor), mTrackCount(AMediaExtractor_getTrackCount(mExtractor)) {
    if (mTrackCount > 0) {
        mTrackCursors.resize(mTrackCount);
        mTrackCursors.resize(mTrackCount);
    }
}

media_status_t MediaSampleReaderNDK::init() {
    for (size_t trackIndex = 0; trackIndex < mTrackCount; trackIndex++) {
        media_status_t status = AMediaExtractor_selectTrack(mExtractor, trackIndex);
        if (status != AMEDIA_OK) {
            LOG(ERROR) << "AMediaExtractor_selectTrack returned error: " << status;
            return status;
        }
    }

    mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
    if (mExtractorTrackIndex >= 0) {
        mTrackCursors[mExtractorTrackIndex].current.set(mExtractorSampleIndex,
                                                        AMediaExtractor_getSampleTime(mExtractor));
    } else if (mTrackCount > 0) {
        // The extractor track index is only allowed to be invalid if there are no tracks.
        LOG(ERROR) << "Track index " << mExtractorTrackIndex << " is invalid for track count "
                   << mTrackCount;
        return AMEDIA_ERROR_MALFORMED;
    }

    return AMEDIA_OK;
}

MediaSampleReaderNDK::~MediaSampleReaderNDK() {
    if (mExtractor != nullptr) {
        AMediaExtractor_delete(mExtractor);
    }
}

bool MediaSampleReaderNDK::advanceExtractor_l() {
    // Reset the "next" sample time whenever the extractor advances past a sample that is current,
    // to ensure that "next" is appropriately updated when the extractor advances over the next
    // sample of that track.
    if (mTrackCursors[mExtractorTrackIndex].current.isSet &&
        mTrackCursors[mExtractorTrackIndex].current.index == mExtractorSampleIndex) {
        mTrackCursors[mExtractorTrackIndex].next.reset();
    }

    if (!AMediaExtractor_advance(mExtractor)) {
        return false;
    }

    mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
    mExtractorSampleIndex++;

    SampleCursor& cursor = mTrackCursors[mExtractorTrackIndex];
    if (mExtractorSampleIndex > cursor.previous.index) {
        if (!cursor.current.isSet) {
            cursor.current.set(mExtractorSampleIndex, AMediaExtractor_getSampleTime(mExtractor));
        } else if (!cursor.next.isSet && mExtractorSampleIndex > cursor.current.index) {
            cursor.next.set(mExtractorSampleIndex, AMediaExtractor_getSampleTime(mExtractor));
        }
    }
    return true;
}

media_status_t MediaSampleReaderNDK::seekExtractorBackwards_l(int64_t targetTimeUs,
                                                              int targetTrackIndex,
                                                              uint64_t targetSampleIndex) {
    if (targetSampleIndex > mExtractorSampleIndex) {
        LOG(ERROR) << "Error: Forward seek is not supported";
        return AMEDIA_ERROR_UNSUPPORTED;
    }

    // AMediaExtractor supports reading negative timestamps but does not support seeking to them.
    const int64_t seekToTimeUs = std::max(targetTimeUs, (int64_t)0);
    media_status_t status =
            AMediaExtractor_seekTo(mExtractor, seekToTimeUs, AMEDIAEXTRACTOR_SEEK_PREVIOUS_SYNC);
    if (status != AMEDIA_OK) {
        LOG(ERROR) << "Unable to seek to " << seekToTimeUs << ", target " << targetTimeUs;
        return status;
    }
    mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
    int64_t sampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);

    while (sampleTimeUs != targetTimeUs || mExtractorTrackIndex != targetTrackIndex) {
        if (!AMediaExtractor_advance(mExtractor)) {
            return AMEDIA_ERROR_END_OF_STREAM;
        }
        mExtractorTrackIndex = AMediaExtractor_getSampleTrackIndex(mExtractor);
        sampleTimeUs = AMediaExtractor_getSampleTime(mExtractor);
    }
    mExtractorSampleIndex = targetSampleIndex;
    return AMEDIA_OK;
}

void MediaSampleReaderNDK::advanceTrack(int trackIndex) {
    std::scoped_lock lock(mExtractorMutex);

    if (trackIndex < 0 || trackIndex >= mTrackCount) {
        LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
        return;
    }

    // Note: Positioning the extractor before advancing the track is needed for two reasons:
    // 1. To enable multiple advances without explicitly letting the extractor catch up.
    // 2. To prevent the extractor from being farther than "next".
    (void)positionExtractorForTrack_l(trackIndex);

    SampleCursor& cursor = mTrackCursors[trackIndex];
    cursor.previous = cursor.current;
    cursor.current = cursor.next;
    cursor.next.reset();
}

media_status_t MediaSampleReaderNDK::positionExtractorForTrack_l(int trackIndex) {
    media_status_t status = AMEDIA_OK;
    const SampleCursor& cursor = mTrackCursors[trackIndex];

    // Seek backwards if the extractor is ahead of the current time.
    if (cursor.current.isSet && mExtractorSampleIndex > cursor.current.index) {
        status = seekExtractorBackwards_l(cursor.current.timeStampUs, trackIndex,
                                          cursor.current.index);
        if (status != AMEDIA_OK) return status;
    }

    // Advance until extractor points to the current sample.
    while (!(cursor.current.isSet && cursor.current.index == mExtractorSampleIndex)) {
        if (!advanceExtractor_l()) {
            return AMEDIA_ERROR_END_OF_STREAM;
        }
    }

    return AMEDIA_OK;
}

media_status_t MediaSampleReaderNDK::getSampleInfoForTrack(int trackIndex, MediaSampleInfo* info) {
    std::scoped_lock lock(mExtractorMutex);

    if (trackIndex < 0 || trackIndex >= mTrackCount) {
        LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
        return AMEDIA_ERROR_INVALID_PARAMETER;
    } else if (info == nullptr) {
        LOG(ERROR) << "MediaSampleInfo pointer is NULL.";
        return AMEDIA_ERROR_INVALID_PARAMETER;
    }

    media_status_t status = positionExtractorForTrack_l(trackIndex);
    if (status == AMEDIA_OK) {
        info->presentationTimeUs = AMediaExtractor_getSampleTime(mExtractor);
        info->flags = AMediaExtractor_getSampleFlags(mExtractor);
        info->size = AMediaExtractor_getSampleSize(mExtractor);
    } else if (status == AMEDIA_ERROR_END_OF_STREAM) {
        info->presentationTimeUs = 0;
        info->flags = SAMPLE_FLAG_END_OF_STREAM;
        info->size = 0;
    }

    return status;
}

media_status_t MediaSampleReaderNDK::readSampleDataForTrack(int trackIndex, uint8_t* buffer,
                                                            size_t bufferSize) {
    std::scoped_lock lock(mExtractorMutex);

    if (trackIndex < 0 || trackIndex >= mTrackCount) {
        LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
        return AMEDIA_ERROR_INVALID_PARAMETER;
    } else if (buffer == nullptr) {
        LOG(ERROR) << "buffer pointer is NULL";
        return AMEDIA_ERROR_INVALID_PARAMETER;
    }

    media_status_t status = positionExtractorForTrack_l(trackIndex);
    if (status != AMEDIA_OK) return status;

    ssize_t sampleSize = AMediaExtractor_getSampleSize(mExtractor);
    if (bufferSize < sampleSize) {
        LOG(ERROR) << "Buffer is too small for sample, " << bufferSize << " vs " << sampleSize;
        return AMEDIA_ERROR_INVALID_PARAMETER;
    }

    ssize_t bytesRead = AMediaExtractor_readSampleData(mExtractor, buffer, bufferSize);
    if (bytesRead < sampleSize) {
        LOG(ERROR) << "Unable to read full sample, " << bytesRead << " vs " << sampleSize;
        return AMEDIA_ERROR_INVALID_PARAMETER;
    }

    return AMEDIA_OK;
}

AMediaFormat* MediaSampleReaderNDK::getFileFormat() {
    return AMediaExtractor_getFileFormat(mExtractor);
}

size_t MediaSampleReaderNDK::getTrackCount() const {
    return mTrackCount;
}

AMediaFormat* MediaSampleReaderNDK::getTrackFormat(int trackIndex) {
    if (trackIndex < 0 || trackIndex >= mTrackCount) {
        LOG(ERROR) << "Invalid trackIndex " << trackIndex << " for trackCount " << mTrackCount;
        return AMediaFormat_new();
    }

    return AMediaExtractor_getTrackFormat(mExtractor, trackIndex);
}

}  // namespace android
+94 −0
Original line number Diff line number Diff line
/*
 * Copyright (C) 2020 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_MEDIA_SAMPLE_H
#define ANDROID_MEDIA_SAMPLE_H

#include <cstdint>

namespace android {

/**
 * Media sample flags.
 * These flags purposely match the media NDK's buffer and extractor flags with one exception. The
 * NDK extractor's flag for encrypted samples (AMEDIAEXTRACTOR_SAMPLE_FLAG_ENCRYPTED) is equal to 2,
 * i.e. the same as SAMPLE_FLAG_CODEC_CONFIG below and NDK's AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG.
 * Sample producers based on the NDK's extractor is responsible for catching those values.
 * Note that currently the media transcoder does not support encrypted samples.
 */
enum : uint32_t {
    SAMPLE_FLAG_SYNC_SAMPLE = 1,
    SAMPLE_FLAG_CODEC_CONFIG = 2,
    SAMPLE_FLAG_END_OF_STREAM = 4,
    SAMPLE_FLAG_PARTIAL_FRAME = 8,
};

// Check that the sample flags have the expected NDK meaning.
namespace {
#include <media/NdkMediaCodec.h>
#include <media/NdkMediaExtractor.h>

static_assert(SAMPLE_FLAG_SYNC_SAMPLE == AMEDIAEXTRACTOR_SAMPLE_FLAG_SYNC,
              "Sample flag mismatch: SYNC_SAMPLE");
static_assert(SAMPLE_FLAG_CODEC_CONFIG == AMEDIACODEC_BUFFER_FLAG_CODEC_CONFIG,
              "Sample flag mismatch: CODEC_CONFIG");
static_assert(SAMPLE_FLAG_END_OF_STREAM == AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM,
              "Sample flag mismatch: END_OF_STREAM");
static_assert(SAMPLE_FLAG_PARTIAL_FRAME == AMEDIACODEC_BUFFER_FLAG_PARTIAL_FRAME,
              "Sample flag mismatch: PARTIAL_FRAME");
}  // anonymous namespace

/**
 * MediaSampleInfo is an object that carries information about a compressed media sample without
 * holding any sample data.
 */
struct MediaSampleInfo {
    /** The sample's presentation timestamp in microseconds. */
    int64_t presentationTimeUs = 0;

    /** The size of the compressed sample data in bytes. */
    size_t size = 0;

    /** Sample flags. */
    uint32_t flags = 0;
};

/**
 * MediaSample holds a compressed media sample in memory.
 */
struct MediaSample {
    /**
     * Byte buffer containing the sample's compressed data.
     * The memory backing this buffer is not managed by the MediaSample object so a separate
     * mechanism to release a buffer is needed between a producer and a consumer.
     */
    const uint8_t* buffer = nullptr;

    /** Offset, in bytes, to the sample's compressed data inside the buffer. */
    size_t dataOffset = 0;

    /**
     * Buffer identifier. This identifier is likely only meaningful to the sample data producer and
     * can be used for reclaiming the buffer once a consumer is done processing it.
     */
    uint32_t bufferId = 0xBAADF00D;

    /** Media sample information. */
    MediaSampleInfo info;
};

}  // namespace android
#endif  // ANDROID_MEDIA_SAMPLE_H
Loading