blob: 5658fbc1d115a65b82b11e41103413af3b1bf8c2 [file]
// Copyright (c) 2012 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.
#include "client_encoder/client_encoder_base.h"
#include <conio.h>
#include <stdio.h>
#include <tchar.h>
#include <string>
#include <vector>
#include "boost/scoped_array.hpp"
#include "client_encoder/buffer_util.h"
#include "client_encoder/http_uploader.h"
#include "client_encoder/webm_encoder.h"
#include "glog/logging.h"
namespace {
enum {
kBadFormat = -3,
kNoMemory = -2,
kInvalidArg = - 1,
kSuccess = 0,
};
const std::string kAgentQueryFragment = "&agent=p";
const std::string kMetadataQueryFragment = "&metadata=1";
const std::string kWebmItagQueryFragment = "&itag=43";
typedef std::vector<std::string> StringVector;
struct WebmEncoderClientConfig {
// Target for HTTP POSTs.
std::string target_url;
// Uploader settings.
webmlive::HttpUploaderSettings uploader_settings;
// WebM encoder settings.
webmlive::WebmEncoderConfig enc_config;
};
} // anonymous namespace
// Prints usage.
void usage(const char** argv) {
printf("Usage: %ls --url <target URL>\n", argv[0]);
printf(" Notes: \n");
printf(" The URL parameter is always required. If no query string is\n");
printf(" present in the URL, the stream_id and stream_name are also\n");
printf(" required.\n");
printf(" The stream_id and stream_name params are required when the\n");
printf(" URL lacks a query string.\n");
printf(" General Options:\n");
printf(" -h | -? | --help Show this message and exit.\n");
printf(" --adev <audio source name> Audio capture device name.\n");
printf(" --form_post Send WebM chunks as file data\n");
printf(" in a form (a la RFC 1867).\n");
printf(" --stream_id <stream ID> Stream ID to include in POST\n");
printf(" query string.\n");
printf(" --stream_name <stream name> Stream name to include in POST\n");
printf(" query string.\n");
printf(" --url <target URL> Target for HTTP Posts.\n");
printf(" --vdev <video source name> Video capture device name.\n");
printf(" Audio source configuration options:\n");
printf(" --adisable Disable audio capture.\n");
printf(" --amanual Attempt manual configuration.\n");
printf(" --achannels <channels> Number of audio channels.\n");
printf(" --arate <sample rate> Audio sample rate.\n");
printf(" --asize <sample size> Audio bits per sample.\n");
printf(" Vorbis Encoder options:\n");
printf(" --vorbis_bitrate <kbps> Average bitrate.\n");
printf(" --vorbis_minimum_bitrate <kbps> Minimum bitrate.\n");
printf(" --vorbis_maximum_bitrate <kbps> Maximum bitrate.\n");
printf(" --vorbis_disable_vbr Disable VBR mode when");
printf(" specifying only an average");
printf(" bitrate.\n");
printf(" --vorbis_iblock_bias <-15.0-0.0> Impulse block bias.\n");
printf(" --vorbis_lowpass_frequency <2-99> Hard-low pass frequency.\n");
printf(" Video source configuration options:\n");
printf(" --vdisable Disable video capture.");
printf(" --vmanual Attempt manual\n");
printf(" configuration.\n");
printf(" --vwidth <width> Width in pixels.\n");
printf(" --vheight <height> Height in pixels.\n");
printf(" --vframe_rate <width> Frames per second.\n");
printf(" VPX Encoder options:\n");
printf(" --vpx_bitrate <kbps> Video bitrate.\n");
printf(" --vpx_decimate <decimate factor> FPS reduction factor.\n");
printf(" --vpx_keyframe_interval <milliseconds> Time between\n");
printf(" keyframes.\n");
printf(" --vpx_min_q <min q value> Quantizer minimum.\n");
printf(" --vpx_max_q <max q value> Quantizer maximum.\n");
printf(" --vpx_noise_sensitivity <0-1> Blurs adjacent frames to\n");
printf(" reduce the noise level of\n");
printf(" input video.\n");
printf(" --vpx_static_threshold <threshold> Static threshold.\n");
printf(" --vpx_speed <speed value> Speed.\n");
printf(" --vpx_threads <num threads> Number of encode threads.\n");
printf(" --vpx_token_partitions <0-3> Number of token\n");
printf(" partitions.\n");
printf(" --vpx_undershoot <undershoot> Undershoot value.\n");
}
// Parses name value pairs in the format name:value from |unparsed_entries|,
// and stores results in |out_map|.
int store_string_map_entries(const StringVector& unparsed_entries,
std::map<std::string, std::string>& out_map) {
using std::string;
using std::vector;
StringVector::const_iterator entry_iter = unparsed_entries.begin();
while (entry_iter != unparsed_entries.end()) {
const string& entry = *entry_iter;
size_t sep = entry.find(":");
if (sep == string::npos) {
// bad header (missing separator, no value)
LOG(ERROR) << "ERROR: cannot parse entry, should be name:value, got="
<< entry.c_str();
return kBadFormat;
}
out_map[entry.substr(0, sep).c_str()] = entry.substr(sep+1);
++entry_iter;
}
return kSuccess;
}
// Returns true when |arg_index| + 1 is <= |argc|, and |argv[arg_index+1]| is
// non-null. Command line parser helper function.
bool arg_has_value(int arg_index, int argc, const char** argv) {
const int val_index = arg_index + 1;
const bool has_value = ((val_index < argc) && (argv[val_index] != NULL));
if (!has_value) {
LOG(WARNING) << "argument missing value: " << argv[arg_index];
}
return has_value;
}
// Parses command line and stores user settings.
void parse_command_line(int argc, const char** argv,
WebmEncoderClientConfig& config) {
StringVector unparsed_headers;
StringVector unparsed_vars;
webmlive::HttpUploaderSettings& uploader_settings = config.uploader_settings;
webmlive::WebmEncoderConfig& enc_config = config.enc_config;
config.uploader_settings.post_mode = webmlive::HTTP_POST;
for (int i = 1; i < argc; ++i) {
if (!strcmp("-h", argv[i]) || !strcmp("-?", argv[i]) ||
!strcmp("--help", argv[i])) {
usage(argv);
exit(EXIT_SUCCESS);
} else if (!strcmp("--url", argv[i]) && arg_has_value(i, argc, argv)) {
config.target_url = argv[++i];
} else if (!strcmp("--header", argv[i]) && arg_has_value(i, argc, argv)) {
unparsed_headers.push_back(argv[++i]);
} else if (!strcmp("--var", argv[i]) && arg_has_value(i, argc, argv)) {
unparsed_vars.push_back(argv[++i]);
} else if (!strcmp("--adev", argv[i]) && arg_has_value(i, argc, argv)) {
enc_config.audio_device_name = argv[++i];
} else if (!strcmp("--achannels", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.requested_audio_config.channels =
static_cast<uint16>(strtol(argv[++i], NULL, 10));
} else if (!strcmp("--adisable", argv[i])) {
enc_config.disable_audio = true;
} else if (!strcmp("--amanual", argv[i])) {
enc_config.ui_opts.manual_audio_config = true;
} else if (!strcmp("--arate", argv[i]) && arg_has_value(i, argc, argv)) {
enc_config.requested_audio_config.sample_rate =
strtol(argv[++i], NULL, 10);
} else if (!strcmp("--asize", argv[i]) && arg_has_value(i, argc, argv)) {
enc_config.requested_audio_config.bits_per_sample =
static_cast<uint16>(strtol(argv[++i], NULL, 10));
} else if (!strcmp("--stream_name", argv[i]) &&
arg_has_value(i, argc, argv)) {
uploader_settings.stream_name = argv[++i];
} else if (!strcmp("--stream_id", argv[i]) &&
arg_has_value(i, argc, argv)) {
uploader_settings.stream_id = argv[++i];
} else if (!strcmp("--form_post", argv[i]) &&
arg_has_value(i, argc, argv)) {
uploader_settings.post_mode = webmlive::HTTP_FORM_POST;
} else if (!strcmp("--vdisable", argv[i])) {
enc_config.disable_video = true;
} else if (!strcmp("--vdev", argv[i]) && arg_has_value(i, argc, argv)) {
enc_config.video_device_name = argv[++i];
} else if (!strcmp("--vmanual", argv[i])) {
enc_config.ui_opts.manual_video_config = true;
} else if (!strcmp("--vwidth", argv[i]) && arg_has_value(i, argc, argv)) {
enc_config.requested_video_config.width = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vheight", argv[i]) && arg_has_value(i, argc, argv)) {
enc_config.requested_video_config.height = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vframe_rate", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.requested_video_config.frame_rate = strtod(argv[++i], NULL);
} else if (!strcmp("--vorbis_bitrate", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vorbis_config.average_bitrate = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vorbis_minimum_bitrate", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vorbis_config.minimum_bitrate = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vorbis_maximum_bitrate", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vorbis_config.maximum_bitrate = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vorbis_disable_vbr", argv[i])) {
enc_config.vorbis_config.bitrate_based_quality = false;
} else if (!strcmp("--vorbis_iblock_bias", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vorbis_config.impulse_block_bias = strtod(argv[++i], NULL);
} else if (!strcmp("--vorbis_lowpass_frequency", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vorbis_config.lowpass_frequency = strtod(argv[++i], NULL);
} else if (!strcmp("--vpx_keyframe_interval", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vpx_config.keyframe_interval = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vpx_bitrate", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vpx_config.bitrate = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vpx_decimate", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vpx_config.decimate = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vpx_min_q", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vpx_config.min_quantizer = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vpx_max_q", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vpx_config.max_quantizer = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vpx_noise_sensitivity", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vpx_config.noise_sensitivity = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vpx_speed", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vpx_config.speed = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vpx_static_threshold", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vpx_config.static_threshold = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vpx_threads", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vpx_config.thread_count = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vpx_token_partitions", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vpx_config.token_partitions = strtol(argv[++i], NULL, 10);
} else if (!strcmp("--vpx_undershoot", argv[i]) &&
arg_has_value(i, argc, argv)) {
enc_config.vpx_config.undershoot = strtol(argv[++i], NULL, 10);
} else {
LOG(WARNING) << "argument unknown or unparseable: " << argv[i];
}
}
// Store user HTTP headers.
store_string_map_entries(unparsed_headers, uploader_settings.headers);
// Store user form variables.
store_string_map_entries(unparsed_vars, uploader_settings.form_variables);
}
// Calls |Init| and |Run| on |uploader| to start the uploader thread, which
// uploads buffers when |UploadBuffer| is called on the uploader.
int start_uploader(WebmEncoderClientConfig* ptr_config,
webmlive::HttpUploader* ptr_uploader) {
int status = ptr_uploader->Init(ptr_config->uploader_settings);
if (status) {
LOG(ERROR) << "uploader Init failed, status=" << status;
return status;
}
if (ptr_config->target_url.find('?') == std::string::npos) {
// When the URL lacks a query string the URL must be reconstructed.
std::ostringstream url;
// Rebuild it with query params included.
url << ptr_config->target_url
<< "?ns=" << ptr_config->uploader_settings.stream_name
<< "&id=" << ptr_config->uploader_settings.stream_id
<< kAgentQueryFragment
<< kWebmItagQueryFragment;
ptr_config->target_url = url.str();
}
// Queue the target URLs.
// Store the target for all but the first upload in |base_url|.
const std::string base_url = ptr_config->target_url;
// Update the target URL to notify the server that the chunk in the first
// upload is metadata.
ptr_config->target_url.append(kMetadataQueryFragment);
ptr_uploader->EnqueueTargetUrl(ptr_config->target_url);
// Now add the URL that's used for all subsequent uploads.
ptr_uploader->EnqueueTargetUrl(base_url);
// Run the uploader (it goes idle and waits for a buffer).
status = ptr_uploader->Run();
if (status) {
LOG(ERROR) << "uploader Run failed, status=" << status;
}
return status;
}
int client_main(WebmEncoderClientConfig* ptr_config) {
webmlive::WebmEncoderConfig& enc_config = ptr_config->enc_config;
webmlive::HttpUploader uploader;
// Init the WebM encoder.
webmlive::WebmEncoder encoder;
int status = encoder.Init(enc_config, &uploader);
if (status) {
LOG(ERROR) << "WebmEncoder Run failed, status=" << status;
return EXIT_FAILURE;
}
// Start the uploader thread.
status = start_uploader(ptr_config, &uploader);
if (status) {
LOG(ERROR) << "start_uploader failed, status=" << status;
return EXIT_FAILURE;
}
// Start the WebM encoder.
status = encoder.Run();
if (status) {
LOG(ERROR) << "start_encoder failed, status=" << status;
uploader.Stop();
return EXIT_FAILURE;
}
webmlive::HttpUploaderStats stats;
printf("\nPress the any key to quit...\n");
while (!_kbhit()) {
// Output current duration and upload progress
if (uploader.GetStats(&stats) == webmlive::HttpUploader::kSuccess) {
printf("\rencoded duration: %04f seconds, uploaded: %I64d @ %d kBps",
(encoder.encoded_duration() / 1000.0),
stats.bytes_sent_current + stats.total_bytes_uploaded,
static_cast<int>(stats.bytes_per_second / 1000));
}
Sleep(100);
}
LOG(INFO) << "stopping encoder...";
encoder.Stop();
LOG(INFO) << "stopping uploader...";
uploader.Stop();
return EXIT_SUCCESS;
}
int main(int argc, const char** argv) {
google::InitGoogleLogging(argv[0]);
WebmEncoderClientConfig config;
parse_command_line(argc, argv, config);
// validate params
if (config.target_url.empty()) {
LOG(ERROR) << "The URL parameter is required!";
usage(argv);
return EXIT_FAILURE;
}
// Confirm |stream_id| and |stream_name| are present when no query string
// is present in |target_url|.
if ((config.uploader_settings.stream_id.empty() ||
config.uploader_settings.stream_name.empty()) &&
config.target_url.find('?') == std::string::npos) {
LOG(ERROR) << "stream_id and stream_name are required when the target "
<< "URL lacks a query string!\n";
return EXIT_FAILURE;
}
LOG(INFO) << "url: " << config.target_url.c_str();
int exit_code = client_main(&config);
google::ShutdownGoogleLogging();
return exit_code;
}
// We build with BOOST_NO_EXCEPTIONS defined; boost will call this function
// instead of throwing. We must stop execution here.
void boost::throw_exception(const std::exception& e) {
LOG(FATAL) << "boost threw! e.what=" << e.what();
exit(EXIT_FAILURE);
}