in vireo/tools/validate/main.cpp [56:275]
int main(int argc, const char* argv[]) {
int threads = 0;
int last_arg = 1;
const string name = common::Path::Filename(argv[0]);
for (int i = 1; i < argc; ++i) {
if (strcmp(argv[i], "-threads") == 0) {
int arg_threads = atoi(argv[++i]);
if (arg_threads < 0 || arg_threads > 64) {
cerr << "Invalid number of threads" << endl;
return 1;
}
threads = (uint16_t)arg_threads;
last_arg = i + 1;
} else if (strcmp(argv[i], "--version") == 0) {
cout << name << " version " << VALIDATE_VERSION << " (based on vireo " << VIREO_VERSION << ")" << endl;
return 0;
} else if (strcmp(argv[i], "--help") == 0) {
cout << "Usage: " << name << " [options] < infile" << endl;
cout << "\nOptions:" << endl;
cout << "-threads:\tnumber of threads (default: 0)" << endl;
cout << "--help:\t\tshow usage" << endl;
cout << "--version:\tshow version" << endl;
return 0;
} else {
#if (USE_STDIN)
cerr << "Invalid argument: " << argv[i] << " (--help for usage)" << endl;
return 1;
#endif
}
}
__try {
// Read stdin into buffer
static const size_t kSize_Default = 512 * 1024;
common::Data32 tmp_buffer(new uint8_t[65536], 65536, [](uint8_t* p) { delete[] p; });
unique_ptr<common::Data32> buffer;
#if (USE_STDIN)
int fd = fcntl(STDIN_FILENO, F_DUPFD, 0);
#else
int fd = open(argv[last_arg], O_RDONLY);
#endif
for (uint16_t i = 0, max_i = 10000; i < max_i; ++i) {
size_t read_size = read((int)fd, (void*)tmp_buffer.data(), (size_t)tmp_buffer.capacity());
if (!read_size) {
break;
}
tmp_buffer.set_bounds(0, (uint32_t)read_size);
if (!buffer.get()) {
buffer.reset(new common::Data32(new uint8_t[kSize_Default], kSize_Default, [](uint8_t* p) { delete[] p; }));
buffer->set_bounds(0, 0);
} else if (read_size + buffer->b() > buffer->capacity()) {
uint32_t new_capacity = buffer->capacity() + ((buffer->b() + (uint32_t)read_size + kSize_Default - 1) / kSize_Default) * kSize_Default;
auto new_buffer = new common::Data32(new uint8_t[new_capacity], new_capacity, [](uint8_t* p) { delete[] p; });
new_buffer->copy(*buffer);
buffer.reset(new_buffer);
}
CHECK(buffer->a() + tmp_buffer.count() <= buffer->capacity());
buffer->set_bounds(buffer->b(), buffer->b());
buffer->copy(tmp_buffer);
buffer->set_bounds(0, buffer->b());
};
close(fd);
THROW_IF(!buffer.get() || !buffer->count(), Invalid);
vireo::demux::Movie movie(move(*buffer));
THROW_IF(movie.file_type() != FileType::MP4 && movie.file_type() != FileType::MP2TS, Unsupported);
THROW_IF(movie.video_track.count() >= kMaxSampleCount, Unsafe);
THROW_IF(movie.audio_track.count() >= kMaxSampleCount, Unsafe);
const auto& video_settings = movie.video_track.settings();
const auto& audio_settings = movie.audio_track.settings();
THROW_IF(video_settings.codec == settings::Video::Codec::VP8, Unsupported);
THROW_IF(video_settings.codec == settings::Video::Codec::MPEG4, Unsupported);
THROW_IF(video_settings.codec == settings::Video::Codec::ProRes, Unsupported);
THROW_IF(video_settings.codec != settings::Video::Codec::H264, Unsupported);
if (movie.audio_track.count()) {
THROW_IF(audio_settings.codec == settings::Audio::Codec::AAC_Main, Unsupported);
THROW_IF(audio_settings.codec == settings::Audio::Codec::Vorbis, Unsupported);
THROW_IF(settings::Audio::IsPCM(audio_settings.codec), Unsupported);
THROW_IF(!settings::Audio::IsAAC(audio_settings.codec), Unsupported);
}
THROW_IF(!movie.video_track.count(), Invalid);
THROW_IF(!movie.video_track(0).keyframe, Invalid);
THROW_IF(!common::EditBox::Valid(movie.video_track.edit_boxes()), Unsupported);
THROW_IF(!common::EditBox::Valid(movie.audio_track.edit_boxes()), Unsupported);
tuple<settings::Video, settings::Audio> metadata = make_tuple(video_settings, audio_settings);
// Cache all nal units
vector<encode::Sample> video_samples;
vector<encode::Sample> audio_samples;
for (const auto& sample: movie.video_track) {
if (!video_samples.empty()) {
THROW_IF(sample.dts <= video_samples.back().dts, Invalid, "Non-increasing DTS values in video track (" << sample.dts << " >= " << video_samples.back().dts << ")");
}
video_samples.push_back((encode::Sample) {
sample.pts,
sample.dts,
sample.keyframe,
sample.type,
sample.nal()
});
}
for (const auto& sample: movie.audio_track) {
if (!audio_samples.empty()) {
THROW_IF(sample.dts <= audio_samples.back().dts, Invalid, "Non-increasing DTS values in audio track (" << sample.dts << " >= " << audio_samples.back().dts << ")");
THROW_IF(sample.pts <= audio_samples.back().pts, Invalid, "Non-increasing PTS values in audio track (" << sample.pts << " >= " << audio_samples.back().pts << ")");
}
audio_samples.push_back((encode::Sample) {
sample.pts,
sample.dts,
sample.keyframe,
sample.type,
sample.nal()
});
}
// Video decode setup
std::atomic<uint16_t> total_decoded_frames(0);
auto decode_frames = [&video_samples, &metadata, &total_decoded_frames](uint64_t start_dts, uint64_t end_dts){
vector<decode::Sample> samples_to_decode;
for (const auto& sample: video_samples) {
if (sample.dts < start_dts) {
continue;
}
if (sample.dts > end_dts) {
THROW_IF(!sample.keyframe, Invalid);
break;
}
samples_to_decode.push_back((decode::Sample){ sample.pts, sample.dts, sample.keyframe, sample.type, [&]() { return sample.nal; } });
}
// we need to decode every GOP individually to make sure we can decode after chunking
auto decode_gop = [&samples_to_decode, &metadata, &total_decoded_frames](uint32_t start_index, uint32_t end_index) {
const auto& video_settings = get<0>(metadata);
THROW_IF(start_index >= end_index, InvalidArguments);
THROW_IF(end_index > samples_to_decode.size(), InvalidArguments);
const uint32_t gop_size = end_index - start_index;
THROW_IF(gop_size > security::kMaxGOPSize, Unsafe,
"GOP is too large (frames [" << start_index << ", " << end_index << ") - max allowed = " << security::kMaxGOPSize << ")");
auto from = samples_to_decode.begin() + start_index;
auto until = samples_to_decode.begin() + end_index;
vector<decode::Sample> gop(from, until);
functional::Video<decode::Sample> video_track(gop, video_settings);
decode::Video video_decoder(video_track);
for (auto frame: video_decoder) {
frame.yuv();
total_decoded_frames.fetch_add(1);
}
};
uint32_t start_index = 0;
for (uint32_t index = 1; index <= samples_to_decode.size(); ++index) {
if (index == samples_to_decode.size() || samples_to_decode[index].keyframe) {
decode_gop(start_index, index);
start_index = index;
}
}
};
// Decode all audio samples
if (audio_samples.size()) {
vector<decode::Sample> samples_to_decode;
for (const auto& sample: audio_samples) {
samples_to_decode.push_back((decode::Sample){ sample.pts, sample.dts, sample.keyframe, sample.type, [&]() { return sample.nal; } });
}
const auto& audio_settings = get<1>(metadata);
functional::Audio<decode::Sample> audio_track(samples_to_decode, audio_settings);
decode::Audio audio_decoder(audio_track);
for (auto sound: audio_decoder) {
sound.pcm();
}
}
// Decode all video samples
if (video_samples.size()) {
const uint64_t final_dts = video_samples.back().dts;
if (threads == 0) {
decode_frames(0, final_dts);
} else {
vector<std::future<void>> futures;
const uint64_t thread_duration = common::round_divide(final_dts, (uint64_t)1, (uint64_t)threads);
uint64_t start_dts = 0;
uint64_t end_dts = thread_duration;
uint64_t prev_dts = 0;
for (const auto& sample: video_samples) {
if (sample.dts == final_dts) {
futures.push_back(std::async(std::launch::async, decode_frames, start_dts, final_dts));
} else if (sample.keyframe && sample.dts > end_dts) {
futures.push_back(std::async(std::launch::async, decode_frames, start_dts, prev_dts));
start_dts = sample.dts;
end_dts = start_dts + thread_duration;
}
prev_dts = sample.dts;
}
for (auto& running_future: futures) {
if (running_future.valid()) {
running_future.get();
}
}
}
CHECK(total_decoded_frames.load() == video_samples.size());
}
cout << "success" << endl;
} __catch (std::exception& e) {
string err = string(e.what());
cout << "fail: " << err << endl;
if (err.find("!intra_decode_refresh") != std::string::npos) { // TODO: remove once MEDIASERV-4386 is resolved
return 2; // return a special error code for !intra_decode_refresh to track occurrence of l-smash bug (MEDIASERV-4386)
}
return 1;
}
return 0;
}