blob: 1f5649e6e19257bd8079a8170ffaa9cbb804b13f [file] [log] [blame]
/*
* Copyright (C) 2021 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.
*/
#include <android-base/macros.h>
#include <fcntl.h>
#include <getopt.h>
#include <media/MediaTranscoder.h>
#include <media/NdkCommon.h>
using namespace android;
#define ERR_MSG(fmt, ...) fprintf(stderr, "Error: " fmt "\n", ##__VA_ARGS__)
class TranscoderCallbacks : public MediaTranscoder::CallbackInterface {
public:
media_status_t waitForTranscodingFinished() {
std::unique_lock<std::mutex> lock(mMutex);
while (!mFinished) {
mCondition.wait(lock);
}
return mStatus;
}
private:
virtual void onFinished(const MediaTranscoder* /*transcoder*/) override {
notifyTranscoderFinished(AMEDIA_OK);
}
virtual void onError(const MediaTranscoder* /*transcoder*/, media_status_t error) override {
ERR_MSG("Transcoder failed with error %d", error);
notifyTranscoderFinished(error);
}
virtual void onProgressUpdate(const MediaTranscoder* /*transcoder*/,
int32_t /*progress*/) override {}
virtual void onCodecResourceLost(
const MediaTranscoder* /*transcoder*/,
const std::shared_ptr<ndk::ScopedAParcel>& /*pausedState*/) override {
ERR_MSG("Transcoder lost codec resource while transcoding");
notifyTranscoderFinished(AMEDIACODEC_ERROR_INSUFFICIENT_RESOURCE);
}
virtual void onHeartBeat(const MediaTranscoder* /*transcoder*/) override {}
void notifyTranscoderFinished(media_status_t status) {
std::unique_lock<std::mutex> lock(mMutex);
mFinished = true;
mStatus = status;
mCondition.notify_all();
}
std::mutex mMutex;
std::condition_variable mCondition;
bool mFinished = false;
media_status_t mStatus = AMEDIA_OK;
};
struct TranscodeConfig {
std::string srcFile;
std::string dstFile;
std::string dstCodec{AMEDIA_MIMETYPE_VIDEO_AVC};
int32_t bitrate = -1;
};
static int transcode(const struct TranscodeConfig& config) {
auto callbacks = std::make_shared<TranscoderCallbacks>();
auto transcoder = MediaTranscoder::create(callbacks, -1 /*heartBeatIntervalUs*/);
const int srcFd = open(config.srcFile.c_str(), O_RDONLY);
if (srcFd <= 0) {
ERR_MSG("Unable to open source file %s", config.srcFile.c_str());
return AMEDIA_ERROR_INVALID_PARAMETER;
}
media_status_t status = transcoder->configureSource(srcFd);
close(srcFd);
if (status != AMEDIA_OK) {
ERR_MSG("configureSource returned error %d", status);
return status;
}
std::vector<std::shared_ptr<AMediaFormat>> trackFormats = transcoder->getTrackFormats();
if (trackFormats.size() <= 0) {
ERR_MSG("No tracks found in source file");
return AMEDIA_ERROR_MALFORMED;
}
for (int i = 0; i < trackFormats.size(); ++i) {
AMediaFormat* dstFormat = nullptr;
const char* mime = nullptr;
AMediaFormat_getString(trackFormats[i].get(), AMEDIAFORMAT_KEY_MIME, &mime);
if (strncmp(mime, "video/", 6) == 0) {
dstFormat = AMediaFormat_new();
AMediaFormat_setString(dstFormat, AMEDIAFORMAT_KEY_MIME, config.dstCodec.c_str());
if (config.bitrate > 0) {
AMediaFormat_setInt32(dstFormat, AMEDIAFORMAT_KEY_BIT_RATE, config.bitrate);
}
}
status = transcoder->configureTrackFormat(i, dstFormat);
if (dstFormat != nullptr) {
AMediaFormat_delete(dstFormat);
}
if (status != AMEDIA_OK) {
ERR_MSG("configureTrack returned error %d", status);
return status;
}
}
// Note: Overwrites existing file.
const int dstFd = open(config.dstFile.c_str(), O_WRONLY | O_CREAT, S_IRUSR | S_IWUSR);
if (dstFd <= 0) {
ERR_MSG("Unable to open destination file %s", config.dstFile.c_str());
return AMEDIA_ERROR_INVALID_PARAMETER;
}
status = transcoder->configureDestination(dstFd);
close(dstFd);
if (status != AMEDIA_OK) {
ERR_MSG("configureDestination returned error %d", status);
return status;
}
status = transcoder->start();
if (status != AMEDIA_OK) {
ERR_MSG("start returned error %d", status);
return status;
}
return callbacks->waitForTranscodingFinished();
}
// Options.
static const struct option kLongOpts[] = {{"help", no_argument, nullptr, 'h'},
{"codec", required_argument, nullptr, 'c'},
{"bitrate", required_argument, nullptr, 'b'},
{0, 0, 0, 0}};
static const char kShortOpts[] = "hc:b:";
static void printUsageAndExit() {
const char* usage =
" -h / --help : Print this usage message and exit.\n"
" -c / --codec : Specify output video codec type using MediaFormat codec mime "
"type.\n"
" Defaults to \"video/avc\".\n"
" -b / --bitrate : Specify output video bitrate in bits per second.\n"
" Defaults to estimating and preserving the original bitrate.\n"
"";
printf("Usage: %s [-h] [-c CODEC] <srcfile> <dstfile>\n%s", getprogname(), usage);
exit(-1);
}
int main(int argc, char** argv) {
int c;
TranscodeConfig config;
while ((c = getopt_long(argc, argv, kShortOpts, kLongOpts, nullptr)) >= 0) {
switch (c) {
case 'c':
config.dstCodec.assign(optarg);
break;
case 'b':
config.bitrate = atoi(optarg);
if (config.bitrate <= 0) {
ERR_MSG("Bitrate must an integer larger than zero.");
printUsageAndExit();
}
break;
case '?':
FALLTHROUGH_INTENDED;
case 'h':
FALLTHROUGH_INTENDED;
default:
printUsageAndExit();
break;
}
}
if (optind > (argc - 2)) {
ERR_MSG("Source and destination file not specified");
printUsageAndExit();
}
config.srcFile.assign(argv[optind++]);
config.dstFile.assign(argv[optind]);
return transcode(config);
}