#define OUTPUT_FREQUENCY 48000 // Currently needs to be exactly 48000, since bmusb outputs in that.
#define MAX_FPS 60
-#define WIDTH 1280
-#define HEIGHT 720
#define FAKE_FPS 25 // Must be an integer.
#define MAX_VIDEO_CARDS 16
#define MAX_ALSA_CARDS 16
// Long options that have no corresponding short option.
enum LongOption {
- OPTION_MULTICHANNEL = 1000,
+ OPTION_HELP = 1000,
+ OPTION_MULTICHANNEL,
OPTION_MIDI_MAPPING,
OPTION_FAKE_CARDS_AUDIO,
OPTION_HTTP_UNCOMPRESSED_VIDEO,
{
fprintf(stderr, "Usage: nageru [OPTION]...\n");
fprintf(stderr, "\n");
- fprintf(stderr, " -h, --help print usage information\n");
+ fprintf(stderr, " --help print usage information\n");
+ fprintf(stderr, " -w, --width output width in pixels (default 1280)\n");
+ fprintf(stderr, " -h, --height output height in pixels (default 720)\n");
fprintf(stderr, " -c, --num-cards set number of input cards (default 2)\n");
fprintf(stderr, " -t, --theme=FILE choose theme (default theme.lua)\n");
fprintf(stderr, " -I, --theme-dir=DIR search for theme in this directory (can be given multiple times)\n");
void parse_flags(int argc, char * const argv[])
{
static const option long_options[] = {
- { "help", no_argument, 0, 'h' },
+ { "help", no_argument, 0, OPTION_HELP },
+ { "width", required_argument, 0, 'w' },
+ { "height", required_argument, 0, 'h' },
{ "num-cards", required_argument, 0, 'c' },
{ "theme", required_argument, 0, 't' },
{ "theme-dir", required_argument, 0, 'I' },
vector<string> theme_dirs;
for ( ;; ) {
int option_index = 0;
- int c = getopt_long(argc, argv, "c:t:I:v:m:M:", long_options, &option_index);
+ int c = getopt_long(argc, argv, "c:t:I:v:m:M:w:h:", long_options, &option_index);
if (c == -1) {
break;
}
switch (c) {
+ case 'w':
+ global_flags.width = atoi(optarg);
+ break;
+ case 'h':
+ global_flags.height = atoi(optarg);
+ break;
case 'c':
global_flags.num_cards = atoi(optarg);
break;
case OPTION_NO_FLUSH_PBOS:
global_flags.flush_pbos = false;
break;
- case 'h':
+ case OPTION_HELP:
usage();
exit(0);
default:
global_flags.theme_dirs = theme_dirs;
}
+ // In reality, we could probably do with any even value (we subsample
+ // by two in some places), but it's better to be on the safe side
+ // wrt. video codecs and such. (I'd set 16 if I could, but 1080 isn't
+ // divisible by 16.)
+ if (global_flags.width <= 0 || (global_flags.width % 8) != 0 ||
+ global_flags.height <= 0 || (global_flags.height % 8) != 0) {
+ fprintf(stderr, "ERROR: --width and --height must be positive integers divisible by 8\n");
+ exit(1);
+ }
+
for (pair<int, int> mapping : global_flags.default_stream_mapping) {
if (mapping.second >= global_flags.num_cards) {
fprintf(stderr, "ERROR: Signal %d mapped to card %d, which doesn't exist (try adjusting --num-cards)\n",
#include "defs.h"
struct Flags {
+ int width = 1280, height = 720;
int num_cards = 2;
std::string va_display;
bool fake_cards_audio = false;
// Allocate the height; the most important part is to keep the main displays
// at the right aspect if at all possible.
double me_width = ui->me_preview->width();
- double me_height = me_width * double(HEIGHT) / double(WIDTH) + ui->label_preview->height() + ui->preview_vertical_layout->spacing();
+ double me_height = me_width * double(global_flags.height) / double(global_flags.width) + ui->label_preview->height() + ui->preview_vertical_layout->spacing();
// TODO: Scale the widths when we need to do this.
if (me_height / double(height) > 0.8) {
double preview_label_height = previews[0]->title_bar->geometry().height() +
previews[0]->main_vertical_layout->spacing();
int preview_total_width = ui->preview_displays->geometry().width() - (previews.size() - 1) * ui->preview_displays->spacing();
- double preview_height = min(remaining_height - preview_label_height, (preview_total_width / double(previews.size())) * double(HEIGHT) / double(WIDTH));
+ double preview_height = min(remaining_height - preview_label_height, (preview_total_width / double(previews.size())) * double(global_flags.height) / double(global_flags.width));
remaining_height -= preview_height + preview_label_height + ui->vertical_layout->spacing();
ui->vertical_layout->setStretch(0, lrintf(me_height));
ui->compact_audio_layout->setStretch(2, lrintf(preview_height + preview_label_height));
// Set the widths for the previews.
- double preview_width = preview_height * double(WIDTH) / double(HEIGHT);
+ double preview_width = preview_height * double(global_flags.width) / double(global_flags.height);
for (unsigned i = 0; i < previews.size(); ++i) {
ui->preview_displays->setStretch(i, lrintf(preview_width));
}
inout_format.gamma_curve = GAMMA_sRGB;
// Display chain; shows the live output produced by the main chain (its RGBA version).
- display_chain.reset(new EffectChain(WIDTH, HEIGHT, resource_pool.get()));
+ display_chain.reset(new EffectChain(global_flags.width, global_flags.height, resource_pool.get()));
check_error();
- display_input = new FlatInput(inout_format, FORMAT_RGB, GL_UNSIGNED_BYTE, WIDTH, HEIGHT); // FIXME: GL_UNSIGNED_BYTE is really wrong.
+ display_input = new FlatInput(inout_format, FORMAT_RGB, GL_UNSIGNED_BYTE, global_flags.width, global_flags.height); // FIXME: GL_UNSIGNED_BYTE is really wrong.
display_chain->add_input(display_input);
display_chain->add_output(inout_format, OUTPUT_ALPHA_FORMAT_POSTMULTIPLIED);
display_chain->set_dither_bits(0); // Don't bother.
display_chain->finalize();
- video_encoder.reset(new VideoEncoder(resource_pool.get(), h264_encoder_surface, global_flags.va_display, WIDTH, HEIGHT, &httpd, global_disk_space_estimator));
+ video_encoder.reset(new VideoEncoder(resource_pool.get(), h264_encoder_surface, global_flags.va_display, global_flags.width, global_flags.height, &httpd, global_disk_space_estimator));
// Start listening for clients only once VideoEncoder has written its header, if any.
httpd.start(9095);
unsigned num_fake_cards = 0;
for ( ; card_index < num_cards; ++card_index, ++num_fake_cards) {
- FakeCapture *capture = new FakeCapture(WIDTH, HEIGHT, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
+ FakeCapture *capture = new FakeCapture(global_flags.width, global_flags.height, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
configure_card(card_index, capture, /*is_fake_capture=*/true);
}
card->is_fake_capture = is_fake_capture;
card->capture->set_frame_callback(bind(&Mixer::bm_frame, this, card_index, _1, _2, _3, _4, _5, _6, _7));
if (card->frame_allocator == nullptr) {
- card->frame_allocator.reset(new PBOFrameAllocator(8 << 20, WIDTH, HEIGHT)); // 8 MB.
+ card->frame_allocator.reset(new PBOFrameAllocator(8 << 20, global_flags.width, global_flags.height)); // 8 MB.
}
card->capture->set_video_frame_allocator(card->frame_allocator.get());
if (card->surface == nullptr) {
CaptureCard *card = &cards[card_index];
if (card->capture->get_disconnected()) {
fprintf(stderr, "Card %u went away, replacing with a fake card.\n", card_index);
- FakeCapture *capture = new FakeCapture(WIDTH, HEIGHT, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
+ FakeCapture *capture = new FakeCapture(global_flags.width, global_flags.height, FAKE_FPS, OUTPUT_FREQUENCY, card_index, global_flags.fake_cards_audio);
configure_card(card_index, capture, /*is_fake_capture=*/true);
card->queue_length_policy.reset(card_index);
card->capture->start_bm_capture();
void Mixer::render_one_frame(int64_t duration)
{
// Get the main chain from the theme, and set its state immediately.
- Theme::Chain theme_main_chain = theme->get_chain(0, pts(), WIDTH, HEIGHT, input_state);
+ Theme::Chain theme_main_chain = theme->get_chain(0, pts(), global_flags.width, global_flags.height, input_state);
EffectChain *chain = theme_main_chain.chain;
theme_main_chain.setup_chain();
//theme_main_chain.chain->enable_phase_timing(true);
assert(got_frame);
// Render main chain.
- GLuint cbcr_full_tex = resource_pool->create_2d_texture(GL_RG8, WIDTH, HEIGHT);
- GLuint rgba_tex = resource_pool->create_2d_texture(GL_RGB565, WIDTH, HEIGHT); // Saves texture bandwidth, although dithering gets messed up.
+ GLuint cbcr_full_tex = resource_pool->create_2d_texture(GL_RG8, global_flags.width, global_flags.height);
+ GLuint rgba_tex = resource_pool->create_2d_texture(GL_RGB565, global_flags.width, global_flags.height); // Saves texture bandwidth, although dithering gets messed up.
GLuint fbo = resource_pool->create_fbo(y_tex, cbcr_full_tex, rgba_tex);
check_error();
- chain->render_to_fbo(fbo, WIDTH, HEIGHT);
+ chain->render_to_fbo(fbo, global_flags.width, global_flags.height);
resource_pool->release_fbo(fbo);
subsample_chroma(cbcr_full_tex, cbcr_tex);
// Set up preview and any additional channels.
for (int i = 1; i < theme->get_num_channels() + 2; ++i) {
DisplayFrame display_frame;
- Theme::Chain chain = theme->get_chain(i, pts(), WIDTH, HEIGHT, input_state); // FIXME: dimensions
+ Theme::Chain chain = theme->get_chain(i, pts(), global_flags.width, global_flags.height, input_state); // FIXME: dimensions
display_frame.chain = chain.chain;
display_frame.setup_chain = chain.setup_chain;
display_frame.ready_fence = fence;
// Extract Cb/Cr.
GLuint fbo = resource_pool->create_fbo(dst_tex);
glBindFramebuffer(GL_FRAMEBUFFER, fbo);
- glViewport(0, 0, WIDTH/2, HEIGHT/2);
+ glViewport(0, 0, global_flags.width/2, global_flags.height/2);
check_error();
glUseProgram(cbcr_program_num);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
check_error();
- float chroma_offset_0[] = { -0.5f / WIDTH, 0.0f };
+ float chroma_offset_0[] = { -0.5f / global_flags.width, 0.0f };
set_uniform_vec2(cbcr_program_num, "foo", "chroma_offset_0", chroma_offset_0);
glBindBuffer(GL_ARRAY_BUFFER, cbcr_vbo);
}
for (unsigned i = 0; i < num_inputs; ++i) {
if (override_bounce) {
- inputs.push_back(new NonBouncingYCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR));
+ inputs.push_back(new NonBouncingYCbCrInput(inout_format, input_ycbcr_format, global_flags.width, global_flags.height, YCBCR_INPUT_SPLIT_Y_AND_CBCR));
} else {
- inputs.push_back(new YCbCrInput(inout_format, input_ycbcr_format, WIDTH, HEIGHT, YCBCR_INPUT_SPLIT_Y_AND_CBCR));
+ inputs.push_back(new YCbCrInput(inout_format, input_ycbcr_format, global_flags.width, global_flags.height, YCBCR_INPUT_SPLIT_Y_AND_CBCR));
}
chain->add_input(inputs.back());
}
X264Encoder::X264Encoder(AVOutputFormat *oformat)
: wants_global_headers(oformat->flags & AVFMT_GLOBALHEADER)
{
- frame_pool.reset(new uint8_t[WIDTH * HEIGHT * 2 * X264_QUEUE_LENGTH]);
+ frame_pool.reset(new uint8_t[global_flags.width * global_flags.height * 2 * X264_QUEUE_LENGTH]);
for (unsigned i = 0; i < X264_QUEUE_LENGTH; ++i) {
- free_frames.push(frame_pool.get() + i * (WIDTH * HEIGHT * 2));
+ free_frames.push(frame_pool.get() + i * (global_flags.width * global_flags.height * 2));
}
encoder_thread = thread(&X264Encoder::encoder_thread_func, this);
}
free_frames.pop();
}
- memcpy(qf.data, data, WIDTH * HEIGHT * 2);
+ memcpy(qf.data, data, global_flags.width * global_flags.height * 2);
{
lock_guard<mutex> lock(mu);
x264_param_t param;
x264_param_default_preset(¶m, global_flags.x264_preset.c_str(), global_flags.x264_tune.c_str());
- param.i_width = WIDTH;
- param.i_height = HEIGHT;
+ param.i_width = global_flags.width;
+ param.i_height = global_flags.height;
param.i_csp = X264_CSP_NV12;
param.b_vfr_input = 1;
param.i_timebase_num = 1;
pic.img.i_csp = X264_CSP_NV12;
pic.img.i_plane = 2;
pic.img.plane[0] = qf.data;
- pic.img.i_stride[0] = WIDTH;
- pic.img.plane[1] = qf.data + WIDTH * HEIGHT;
- pic.img.i_stride[1] = WIDTH / 2 * sizeof(uint16_t);
+ pic.img.i_stride[0] = global_flags.width;
+ pic.img.plane[1] = qf.data + global_flags.width * global_flags.height;
+ pic.img.i_stride[1] = global_flags.width / 2 * sizeof(uint16_t);
pic.opaque = reinterpret_cast<void *>(intptr_t(qf.duration));
input_pic = &pic;