You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
211 lines
6.7 KiB
211 lines
6.7 KiB
4 months ago
|
/*
|
||
|
* 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);
|
||
|
}
|