blob: 5868ffd266b6a8a4e5e4d4016f168eb975a5cf6a [file] [log] [blame]
/*
* 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 "MediaTranscoder"
#include <android-base/logging.h>
#include <binder/Parcel.h>
#include <fcntl.h>
#include <media/MediaSampleReaderNDK.h>
#include <media/MediaSampleWriter.h>
#include <media/MediaTranscoder.h>
#include <media/NdkCommon.h>
#include <media/PassthroughTrackTranscoder.h>
#include <media/VideoTrackTranscoder.h>
#include <unistd.h>
namespace android {
static AMediaFormat* mergeMediaFormats(AMediaFormat* base, AMediaFormat* overlay) {
if (base == nullptr || overlay == nullptr) {
LOG(ERROR) << "Cannot merge null formats";
return nullptr;
}
AMediaFormat* format = AMediaFormat_new();
if (AMediaFormat_copy(format, base) != AMEDIA_OK) {
AMediaFormat_delete(format);
return nullptr;
}
// Note: AMediaFormat does not expose a function for appending values from another format or for
// iterating over all values and keys in a format. Instead we define a static list of known keys
// along with their value types and copy the ones that are present. A better solution would be
// to either implement required functions in NDK or to parse the overlay format's string
// representation and copy all existing keys.
static const AMediaFormatUtils::EntryCopier kSupportedFormatEntries[] = {
ENTRY_COPIER(AMEDIAFORMAT_KEY_MIME, String),
ENTRY_COPIER(AMEDIAFORMAT_KEY_DURATION, Int64),
ENTRY_COPIER(AMEDIAFORMAT_KEY_WIDTH, Int32),
ENTRY_COPIER(AMEDIAFORMAT_KEY_HEIGHT, Int32),
ENTRY_COPIER(AMEDIAFORMAT_KEY_BIT_RATE, Int32),
ENTRY_COPIER(AMEDIAFORMAT_KEY_PROFILE, Int32),
ENTRY_COPIER(AMEDIAFORMAT_KEY_LEVEL, Int32),
ENTRY_COPIER(AMEDIAFORMAT_KEY_COLOR_FORMAT, Int32),
ENTRY_COPIER(AMEDIAFORMAT_KEY_COLOR_RANGE, Int32),
ENTRY_COPIER(AMEDIAFORMAT_KEY_COLOR_STANDARD, Int32),
ENTRY_COPIER(AMEDIAFORMAT_KEY_COLOR_TRANSFER, Int32),
ENTRY_COPIER(AMEDIAFORMAT_KEY_FRAME_RATE, Int32),
ENTRY_COPIER(AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, Int32),
ENTRY_COPIER(AMEDIAFORMAT_KEY_PRIORITY, Int32),
ENTRY_COPIER2(AMEDIAFORMAT_KEY_OPERATING_RATE, Float, Int32),
};
const size_t entryCount = sizeof(kSupportedFormatEntries) / sizeof(kSupportedFormatEntries[0]);
AMediaFormatUtils::CopyFormatEntries(overlay, format, kSupportedFormatEntries, entryCount);
return format;
}
void MediaTranscoder::sendCallback(media_status_t status) {
// If the transcoder is already cancelled explicitly, don't send any error callbacks.
// Tracks and sample writer will report errors for abort. However, currently we can't
// tell it apart from real errors. Ideally we still want to report real errors back
// to client, as there is a small chance that explicit abort and the real error come
// at around the same time, we should report that if abort has a specific error code.
// On the other hand, if the transcoder actually finished (status is AMEDIA_OK) at around
// the same time of the abort, we should still report the finish back to the client.
if (mCancelled && status != AMEDIA_OK) {
return;
}
bool expected = false;
if (mCallbackSent.compare_exchange_strong(expected, true)) {
if (status == AMEDIA_OK) {
mCallbacks->onFinished(this);
} else {
mCallbacks->onError(this, status);
}
// Transcoding is done and the callback to the client has been sent, so tear down the
// pipeline but do it asynchronously to avoid deadlocks. If an error occurred, client
// should clean up the file.
std::thread asyncCancelThread{[self = shared_from_this()] { self->cancel(); }};
asyncCancelThread.detach();
}
}
void MediaTranscoder::onTrackFormatAvailable(const MediaTrackTranscoder* transcoder) {
LOG(INFO) << "TrackTranscoder " << transcoder << " format available.";
std::scoped_lock lock{mTracksAddedMutex};
// Ignore duplicate format change.
if (mTracksAdded.count(transcoder) > 0) {
return;
}
// Add track to the writer.
auto consumer = mSampleWriter->addTrack(transcoder->getOutputFormat());
if (consumer == nullptr) {
LOG(ERROR) << "Unable to add track to sample writer.";
sendCallback(AMEDIA_ERROR_UNKNOWN);
return;
}
MediaTrackTranscoder* mutableTranscoder = const_cast<MediaTrackTranscoder*>(transcoder);
mutableTranscoder->setSampleConsumer(consumer);
mTracksAdded.insert(transcoder);
if (mTracksAdded.size() == mTrackTranscoders.size()) {
// Enable sequential access mode on the sample reader to achieve optimal read performance.
// This has to wait until all tracks have delivered their output formats and the sample
// writer is started. Otherwise the tracks will not get their output sample queues drained
// and the transcoder could hang due to one track running out of buffers and blocking the
// other tracks from reading source samples before they could output their formats.
mSampleReader->setEnforceSequentialAccess(true);
LOG(INFO) << "Starting sample writer.";
bool started = mSampleWriter->start();
if (!started) {
LOG(ERROR) << "Unable to start sample writer.";
sendCallback(AMEDIA_ERROR_UNKNOWN);
}
}
}
void MediaTranscoder::onTrackFinished(const MediaTrackTranscoder* transcoder) {
LOG(DEBUG) << "TrackTranscoder " << transcoder << " finished";
}
void MediaTranscoder::onTrackError(const MediaTrackTranscoder* transcoder, media_status_t status) {
LOG(ERROR) << "TrackTranscoder " << transcoder << " returned error " << status;
sendCallback(status);
}
void MediaTranscoder::onFinished(const MediaSampleWriter* writer __unused, media_status_t status) {
LOG((status != AMEDIA_OK) ? ERROR : DEBUG) << "Sample writer finished with status " << status;
sendCallback(status);
}
void MediaTranscoder::onProgressUpdate(const MediaSampleWriter* writer __unused, int32_t progress) {
// Dispatch progress updated to the client.
mCallbacks->onProgressUpdate(this, progress);
}
MediaTranscoder::MediaTranscoder(const std::shared_ptr<CallbackInterface>& callbacks)
: mCallbacks(callbacks) {}
std::shared_ptr<MediaTranscoder> MediaTranscoder::create(
const std::shared_ptr<CallbackInterface>& callbacks,
const std::shared_ptr<const Parcel>& pausedState) {
if (pausedState != nullptr) {
LOG(INFO) << "Initializing from paused state.";
}
if (callbacks == nullptr) {
LOG(ERROR) << "Callbacks cannot be null";
return nullptr;
}
return std::shared_ptr<MediaTranscoder>(new MediaTranscoder(callbacks));
}
media_status_t MediaTranscoder::configureSource(int fd) {
if (fd < 0) {
LOG(ERROR) << "Invalid source fd: " << fd;
return AMEDIA_ERROR_INVALID_PARAMETER;
}
const size_t fileSize = lseek(fd, 0, SEEK_END);
lseek(fd, 0, SEEK_SET);
mSampleReader = MediaSampleReaderNDK::createFromFd(fd, 0 /* offset */, fileSize);
if (mSampleReader == nullptr) {
LOG(ERROR) << "Unable to parse source fd: " << fd;
return AMEDIA_ERROR_UNSUPPORTED;
}
const size_t trackCount = mSampleReader->getTrackCount();
for (size_t trackIndex = 0; trackIndex < trackCount; ++trackIndex) {
AMediaFormat* trackFormat = mSampleReader->getTrackFormat(static_cast<int>(trackIndex));
if (trackFormat == nullptr) {
LOG(ERROR) << "Track #" << trackIndex << " has no format";
return AMEDIA_ERROR_MALFORMED;
}
mSourceTrackFormats.emplace_back(trackFormat, &AMediaFormat_delete);
}
return AMEDIA_OK;
}
std::vector<std::shared_ptr<AMediaFormat>> MediaTranscoder::getTrackFormats() const {
// Return a deep copy of the formats to avoid the caller modifying our internal formats.
std::vector<std::shared_ptr<AMediaFormat>> trackFormats;
for (const std::shared_ptr<AMediaFormat>& sourceFormat : mSourceTrackFormats) {
AMediaFormat* copy = AMediaFormat_new();
AMediaFormat_copy(copy, sourceFormat.get());
trackFormats.emplace_back(copy, &AMediaFormat_delete);
}
return trackFormats;
}
media_status_t MediaTranscoder::configureTrackFormat(size_t trackIndex, AMediaFormat* trackFormat) {
if (mSampleReader == nullptr) {
LOG(ERROR) << "Source must be configured before tracks";
return AMEDIA_ERROR_INVALID_OPERATION;
} else if (trackIndex >= mSourceTrackFormats.size()) {
LOG(ERROR) << "Track index " << trackIndex
<< " is out of bounds. Track count: " << mSourceTrackFormats.size();
return AMEDIA_ERROR_INVALID_PARAMETER;
}
media_status_t status = mSampleReader->selectTrack(trackIndex);
if (status != AMEDIA_OK) {
LOG(ERROR) << "Unable to select track " << trackIndex;
return status;
}
std::shared_ptr<MediaTrackTranscoder> transcoder;
std::shared_ptr<AMediaFormat> format;
if (trackFormat == nullptr) {
transcoder = std::make_shared<PassthroughTrackTranscoder>(shared_from_this());
} else {
const char* srcMime = nullptr;
if (!AMediaFormat_getString(mSourceTrackFormats[trackIndex].get(), AMEDIAFORMAT_KEY_MIME,
&srcMime)) {
LOG(ERROR) << "Source track #" << trackIndex << " has no mime type";
return AMEDIA_ERROR_MALFORMED;
}
if (strncmp(srcMime, "video/", 6) != 0) {
LOG(ERROR) << "Only video tracks are supported for transcoding. Unable to configure "
"track #"
<< trackIndex << " with mime " << srcMime;
return AMEDIA_ERROR_UNSUPPORTED;
}
const char* dstMime = nullptr;
if (AMediaFormat_getString(trackFormat, AMEDIAFORMAT_KEY_MIME, &dstMime)) {
if (strncmp(dstMime, "video/", 6) != 0) {
LOG(ERROR) << "Unable to convert media types for track #" << trackIndex << ", from "
<< srcMime << " to " << dstMime;
return AMEDIA_ERROR_UNSUPPORTED;
}
}
transcoder = VideoTrackTranscoder::create(shared_from_this());
AMediaFormat* mergedFormat =
mergeMediaFormats(mSourceTrackFormats[trackIndex].get(), trackFormat);
if (mergedFormat == nullptr) {
LOG(ERROR) << "Unable to merge source and destination formats";
return AMEDIA_ERROR_UNKNOWN;
}
format = std::shared_ptr<AMediaFormat>(mergedFormat, &AMediaFormat_delete);
}
transcoder->configure(mSampleReader, trackIndex, format);
if (status != AMEDIA_OK) {
LOG(ERROR) << "Configure track transcoder for track #" << trackIndex << " returned error "
<< status;
return status;
}
mTrackTranscoders.emplace_back(std::move(transcoder));
return AMEDIA_OK;
}
media_status_t MediaTranscoder::configureDestination(int fd) {
if (fd < 0) {
LOG(ERROR) << "Invalid destination fd: " << fd;
return AMEDIA_ERROR_INVALID_PARAMETER;
}
if (mSampleWriter != nullptr) {
LOG(ERROR) << "Destination is already configured.";
return AMEDIA_ERROR_INVALID_OPERATION;
}
mSampleWriter = MediaSampleWriter::Create();
const bool initOk = mSampleWriter->init(fd, shared_from_this());
if (!initOk) {
LOG(ERROR) << "Unable to initialize sample writer with destination fd: " << fd;
mSampleWriter.reset();
return AMEDIA_ERROR_UNKNOWN;
}
return AMEDIA_OK;
}
media_status_t MediaTranscoder::start() {
if (mTrackTranscoders.size() < 1) {
LOG(ERROR) << "Unable to start, no tracks are configured.";
return AMEDIA_ERROR_INVALID_OPERATION;
} else if (mSampleWriter == nullptr) {
LOG(ERROR) << "Unable to start, destination is not configured";
return AMEDIA_ERROR_INVALID_OPERATION;
}
// Start transcoders
for (auto& transcoder : mTrackTranscoders) {
bool started = transcoder->start();
if (!started) {
LOG(ERROR) << "Unable to start track transcoder.";
cancel();
return AMEDIA_ERROR_UNKNOWN;
}
}
return AMEDIA_OK;
}
media_status_t MediaTranscoder::pause(std::shared_ptr<const Parcel>* pausedState) {
// TODO: write internal states to parcel.
*pausedState = std::make_shared<Parcel>();
return cancel();
}
media_status_t MediaTranscoder::resume() {
// TODO: restore internal states from parcel.
return start();
}
media_status_t MediaTranscoder::cancel() {
bool expected = false;
if (!mCancelled.compare_exchange_strong(expected, true)) {
// Already cancelled.
return AMEDIA_OK;
}
mSampleWriter->stop();
mSampleReader->setEnforceSequentialAccess(false);
for (auto& transcoder : mTrackTranscoders) {
transcoder->stop();
}
return AMEDIA_OK;
}
} // namespace android