blob: 4402ab187a23fe432df80f6e02eec6624cd1e02f [file] [log] [blame]
/*
* Copyright (c) 2013 The WebM project authors. All Rights Reserved.
*
* Use of this source code is governed by a BSD-style license
* that can be found in the LICENSE file in the root of the source
* tree. An additional intellectual property rights grant can be found
* in the file PATENTS. All contributing project authors may
* be found in the AUTHORS file in the root of the source tree.
*/
/*
* This is an example of an VP8 encoder which can produce VP8 videos with alpha.
* It takes an input file in raw AYUV format, passes it through the encoder, and
* writes the compressed frames to disk in webm format.
*
* The design on how alpha encoding works can be found here: http://goo.gl/wCP1y
*/
#include <cstdio>
#include <cstring>
#include <sstream>
#include <string>
// libwebm parser includes
#include "mkvparser.hpp"
#include "mkvreader.hpp"
// libwebm muxer includes
#include "mkvmuxer.hpp"
#include "mkvmuxerutil.hpp"
#include "mkvwriter.hpp"
using mkvmuxer::uint64;
using mkvmuxer::uint32;
using mkvmuxer::uint8;
namespace {
bool CreateInputFiles(const char* input, int w, int h) {
FILE* const f_input = fopen(input, "rb");
if (!f_input) {
fprintf(stderr, "Failed to open %s for reading", input);
return false;
}
FILE* const f_video = fopen("video.in", "wb");
if (!f_video) {
fprintf(stderr, "Failed to open video.in for write");
return false;
}
FILE* const f_alpha = fopen("alpha.in", "wb");
if (!f_alpha) {
fprintf(stderr, "Failed to open alpha.in for write");
return false;
}
const int uv_w = (1 + w) / 2;
const int uv_h = (1 + h) / 2;
uint8* row = new uint8[w];
bool eof = false;
while (!eof) {
for (int i = 0; i < 6 && !eof; ++i) {
const uint32 width = (i % 3) ? uv_w : w;
const uint32 height = (i % 3) ? uv_h : h;
for (uint32 j = 0; j < height; ++j) {
if (i < 4) {
if (fread(row, 1, width, f_input) != width) {
eof = true;
break;
}
} else {
memset(row, 0x80, width);
}
fwrite(row, 1, width, (i < 3) ? f_video : f_alpha);
}
}
}
fclose(f_video);
fclose(f_alpha);
fclose(f_input);
delete[] row;
return true;
}
bool Encode(const std::string& vpxenc_cmd,
const std::string& vpxenc_options,
int w, int h, std::string codec) {
// TODO(vigneshv): For now, we force the keyframe intervals to a static value
// on both the encodes so that the keyframes are aligned. Ideal way to do
// this is to encode the first file, parse it to determine key frames and
// pass that list to vpxenc for the second file.
std::ostringstream encode_cmd;
encode_cmd << vpxenc_cmd << " --width=" << w << " --height=" << h
<< " --codec=" << codec << " --kf-min-dist=150 --kf-max-dist=150 "
<< vpxenc_options << " -o video.out video.in";
fprintf(stderr, "Running %s\n", encode_cmd.str().c_str());
if (system(encode_cmd.str().c_str()))
return false;
encode_cmd.str(std::string());
encode_cmd << vpxenc_cmd << " --width=" << w << " --height=" << h
<< " --codec=" << codec << " --kf-min-dist=150 --kf-max-dist=150 "
<< vpxenc_options << " -o alpha.out alpha.in";
fprintf(stderr, "Running %s\n", encode_cmd.str().c_str());
if (system(encode_cmd.str().c_str()))
return false;
return true;
}
} // namespace
void Usage() {
printf("Usage: alpha_encoder -i input -o output -h height -w width -b "
"<path_to_vpxenc_binary> [vpxenc_options]\n");
printf("Options:\n");
printf(" -? | --help show help\n");
printf(" -i input file (raw yuva420p only)\n");
printf(" -o output file (webm with alpha)\n");
printf(" -w width of the input file\n");
printf(" -h height of the input file\n");
printf(" -c codec (vp8 or vp9). default is vp8\n");
printf(" -b absolute/relative path of vpxenc binary. "
"default is ../../libvpx/vpxenc\n");
printf(" [vpxenc_options] options to be passed to vpxenc. these options"
" are passed on to vpxenc as is. options to vpxenc should always"
" be in the end (i.e.) after all the aforementioned options\n");
printf("\n");
}
bool Init(const char* output,
mkvmuxer::MkvWriter* writer,
mkvmuxer::Segment* muxer_segment,
mkvparser::MkvReader* reader,
mkvparser::MkvReader* reader_alpha) {
if (reader->Open("video.out")) {
fprintf(stderr, "\n Error while opening video.out.\n");
return false;
}
if (reader_alpha->Open("alpha.out")) {
fprintf(stderr, "\n Error while opening alpha.out.\n");
return false;
}
if (!writer->Open(output)) {
fprintf(stderr, "\n Error while opening output file.\n");
return false;
}
if (!muxer_segment->Init(writer)) {
fprintf(stderr, "\n Could not initialize muxer segment!\n");
return false;
}
return true;
}
bool Cleanup(mkvmuxer::MkvWriter* writer,
mkvparser::MkvReader* reader,
mkvparser::MkvReader* reader_alpha,
mkvmuxer::Segment* muxer_segment,
mkvparser::Segment** parser_segment,
mkvparser::Segment** parser_segment_alpha) {
if (!muxer_segment->Finalize())
fprintf(stderr, "Finalization of segment failed.\n");
delete *parser_segment;
delete *parser_segment_alpha;
writer->Close();
reader->Close();
reader_alpha->Close();
if (remove("video.in") || remove("video.out") ||
remove("alpha.in") || remove("alpha.out")) {
fprintf(stderr, "\n Error while removing temp files.\n");
return false;
}
return true;
}
bool WriteTrack(mkvparser::MkvReader* reader,
mkvparser::MkvReader* reader_alpha,
mkvmuxer::Segment* muxer_segment,
mkvparser::Segment** parser_segment,
mkvparser::Segment** parser_segment_alpha,
std::string codec) {
long long pos = 0;
mkvparser::EBMLHeader ebml_header;
ebml_header.Parse(reader, pos);
long long pos_alpha = 0;
mkvparser::EBMLHeader ebml_header_alpha;
ebml_header_alpha.Parse(reader_alpha, pos_alpha);
long long ret = mkvparser::Segment::CreateInstance(reader,
pos,
*parser_segment);
if (ret) {
fprintf(stderr, "\n Segment::CreateInstance() failed.");
return false;
}
ret = mkvparser::Segment::CreateInstance(reader_alpha,
pos_alpha,
*parser_segment_alpha);
if (ret) {
fprintf(stderr, "\n Segment::CreateInstance() failed.");
return false;
}
ret = (*parser_segment)->Load();
if (ret < 0) {
fprintf(stderr, "\n Segment::Load() failed.");
return false;
}
ret = (*parser_segment_alpha)->Load();
if (ret < 0) {
fprintf(stderr, "\n Segment::Load() failed.");
return false;
}
const mkvparser::SegmentInfo* const segment_info =
(*parser_segment)->GetInfo();
const long long timeCodeScale = segment_info->GetTimeCodeScale();
muxer_segment->set_mode(mkvmuxer::Segment::kFile);
mkvmuxer::SegmentInfo* const info = muxer_segment->GetSegmentInfo();
info->set_timecode_scale(timeCodeScale);
info->set_writing_app("alpha_encoder");
const mkvparser::Tracks* const parser_tracks = (*parser_segment)->GetTracks();
uint64 vid_track = 0; // no track added
const mkvparser::Track* const parser_track =
parser_tracks->GetTrackByIndex(0);
if (!parser_track)
return false;
const mkvparser::VideoTrack* const pVideoTrack =
static_cast<const mkvparser::VideoTrack*>(parser_track);
const char* const track_name = pVideoTrack->GetNameAsUTF8();
const long long width = pVideoTrack->GetWidth();
const long long height = pVideoTrack->GetHeight();
// Add the video track to the muxer
vid_track = muxer_segment->AddVideoTrack(static_cast<int>(width),
static_cast<int>(height),
1);
if (!vid_track) {
fprintf(stderr, "\n Could not add video track.\n");
return false;
}
mkvmuxer::VideoTrack* const video =
static_cast<mkvmuxer::VideoTrack*>(
muxer_segment->GetTrackByNumber(vid_track));
if (!video) {
fprintf(stderr, "\n Could not get video track.\n");
return false;
}
video->set_codec_id((codec == "vp9") ?
mkvmuxer::Tracks::kVp9CodecId : mkvmuxer::Tracks::kVp8CodecId);
if (track_name)
video->set_name(track_name);
video->SetAlphaMode(1);
video->set_max_block_additional_id(1);
return true;
}
bool WriteClusters(mkvparser::MkvReader* reader,
mkvparser::MkvReader* reader_alpha,
mkvmuxer::Segment* muxer_segment,
mkvparser::Segment* parser_segment,
mkvparser::Segment* parser_segment_alpha) {
uint8* data = NULL;
int data_len = 0;
uint8* additional = NULL;
int additional_len = 0;
const mkvparser::Cluster* cluster = parser_segment->GetFirst();
const mkvparser::Cluster* cluster_alpha = parser_segment_alpha->GetFirst();
while (cluster != NULL && !cluster->EOS() &&
cluster_alpha != NULL && !cluster_alpha->EOS()) {
const mkvparser::BlockEntry* block_entry;
long status = cluster->GetFirst(block_entry);
if (status) {
fprintf(stderr, "\n Could not get first block of cluster.\n");
return false;
}
const mkvparser::BlockEntry* block_entry_alpha;
status = cluster_alpha->GetFirst(block_entry_alpha);
if (status) {
fprintf(stderr, "\n Could not get first block of cluster.\n");
return false;
}
while (block_entry != NULL && !block_entry->EOS() &&
block_entry_alpha != NULL && !block_entry_alpha->EOS()) {
const mkvparser::Block* const block = block_entry->GetBlock();
const mkvparser::Block* const block_alpha =
block_entry_alpha->GetBlock();
const long long time_ns = block->GetTime(cluster);
const int frame_count = block->GetFrameCount();
const bool is_key = block->IsKey();
for (int i = 0; i < frame_count; ++i) {
// TODO(vigneshv): Handle altref frames
const mkvparser::Block::Frame& frame = block->GetFrame(i);
const mkvparser::Block::Frame& frame_alpha = block_alpha->GetFrame(i);
if (frame.len > data_len) {
data = static_cast<uint8*>(realloc(data, sizeof(uint8) * frame.len));
if (!data)
return false;
data_len = frame.len;
}
if (frame_alpha.len > additional_len) {
additional = static_cast<uint8*>(
realloc(additional, sizeof(uint8) * frame_alpha.len));
if (!additional)
return false;
additional_len = frame_alpha.len;
}
if (frame.Read(reader, data))
return false;
if (frame_alpha.Read(reader_alpha, additional))
return false;
if (!muxer_segment->AddFrameWithAdditional(data,
frame.len,
additional,
frame_alpha.len,
1, 1,
time_ns,
is_key)) {
fprintf(stderr, "\n Could not add frame.\n");
return false;
}
}
status = cluster->GetNext(block_entry, block_entry);
if (status) {
fprintf(stderr, "\n Could not get next block of cluster.\n");
return false;
}
status = cluster_alpha->GetNext(block_entry_alpha, block_entry_alpha);
if (status) {
fprintf(stderr, "\n Could not get next block of cluster.\n");
return false;
}
}
cluster = parser_segment->GetNext(cluster);
cluster_alpha = parser_segment_alpha->GetNext(cluster_alpha);
}
free(data);
free(additional);
return true;
}
int main(int argc, const char** argv) {
const char* input = NULL;
const char* output = NULL;
std::string vpxenc_cmd = "../../libvpx/vpxenc";
std::string vpxenc_options = "";
std::string codec = "vp8";
long w = -1;
long h = -1;
int i;
// Parse command line parameters
const int argc_check = argc - 1;
for (i = 1; i < argc; ++i) {
char* end;
if (!strcmp("-?", argv[i]) || !strcmp("--help", argv[i])) {
Usage();
return EXIT_SUCCESS;
} else if (!strcmp("-i", argv[i]) && i < argc_check) {
input = argv[++i];
} else if (!strcmp("-o", argv[i]) && i < argc_check) {
output = argv[++i];
} else if (!strcmp("-w", argv[i]) && i < argc_check) {
w = strtol(argv[++i], &end, 10);
} else if (!strcmp("-h", argv[i]) && i < argc_check) {
h = strtol(argv[++i], &end, 10);
} else if (!strcmp("-b", argv[i]) && i < argc_check) {
vpxenc_cmd = argv[++i];
} else if (!strcmp("-c", argv[i]) && i < argc_check) {
codec = argv[++i];
} else {
break;
}
}
// All remaining parameters are to be passed on to vpxenc
for (; i < argc; ++i) {
vpxenc_options += argv[i];
vpxenc_options += " ";
}
// Validate input parameters
if (!input || !output || w == -1 || h == -1) {
Usage();
return EXIT_FAILURE;
}
if (codec != "vp8" && codec != "vp9") {
fprintf(stderr, "Invalid codec: '%s'. Has to be one of 'vp8' or 'vp9'.\n",
codec.c_str());
return EXIT_FAILURE;
}
if (w < 16 || h < 16) {
fprintf(stderr, "Invalid resolution: %ldx%ld", w, h);
return EXIT_FAILURE;
}
mkvparser::MkvReader reader;
mkvparser::MkvReader reader_alpha;
mkvmuxer::MkvWriter writer;
mkvmuxer::Segment muxer_segment;
mkvparser::Segment* parser_segment = NULL;
mkvparser::Segment* parser_segment_alpha = NULL;
if (!CreateInputFiles(input, w, h) ||
!Encode(vpxenc_cmd, vpxenc_options, w, h, codec) ||
!Init(output, &writer, &muxer_segment, &reader, &reader_alpha) ||
!WriteTrack(&reader, &reader_alpha, &muxer_segment,
&parser_segment, &parser_segment_alpha, codec) ||
!WriteClusters(&reader, &reader_alpha, &muxer_segment,
parser_segment, parser_segment_alpha) ||
!Cleanup(&writer, &reader, &reader_alpha, &muxer_segment,
&parser_segment, &parser_segment_alpha))
return EXIT_FAILURE;
return EXIT_SUCCESS;
}