From 0faba3d989c344d3db70f241a06761407d966bf7 Mon Sep 17 00:00:00 2001 From: "Steinar H. Gunderson" Date: Mon, 16 Nov 2015 21:39:54 +0100 Subject: [PATCH 1/1] Support true variable input frame rate instead of hard-coding to 60. --- defs.h | 3 +- h264encode.cpp | 14 +++--- mixer.cpp | 115 ++++++++++++++++++++++++++++++++++++++----------- mixer.h | 16 +++++-- 4 files changed, 111 insertions(+), 37 deletions(-) diff --git a/defs.h b/defs.h index 2b57898..9bed232 100644 --- a/defs.h +++ b/defs.h @@ -1,9 +1,8 @@ #ifndef _DEFS_H #define _DEFS_H -// OUTPUT_FREQUENCY/FPS must be an integer for now. #define OUTPUT_FREQUENCY 48000 -#define FPS 60 +#define MAX_FPS 60 #define WIDTH 1280 #define HEIGHT 720 diff --git a/h264encode.cpp b/h264encode.cpp index d206835..a359726 100644 --- a/h264encode.cpp +++ b/h264encode.cpp @@ -114,7 +114,6 @@ static int frame_width = 176; static int frame_height = 144; static int frame_width_mbaligned; static int frame_height_mbaligned; -static int frame_rate = FPS; static unsigned int frame_bitrate = 0; static unsigned int frame_slices = 1; static double frame_size = 0; @@ -122,7 +121,7 @@ static int initial_qp = 15; //static int initial_qp = 28; static int minimal_qp = 0; static int intra_period = 30; -static int intra_idr_period = FPS; +static int intra_idr_period = MAX_FPS; // About a second; more at lower frame rates. Not ideal. static int ip_period = 3; static int rc_mode = -1; static int rc_default_modes[] = { @@ -619,7 +618,7 @@ static int build_packed_slice_buffer(unsigned char **header_buffer) // // Getting pts and dts right with variable frame rate (VFR) and B-frames can be a // bit tricky. We assume first of all that the frame rate never goes _above_ -// , which gives us a frame period N. The decoder can always decode +// MAX_FPS, which gives us a frame period N. The decoder can always decode // in at least this speed, as long at dts <= pts (the frame is not attempted // presented before it is decoded). Furthermore, we never have longer chains of // B-frames than a fixed constant C. (In a B-frame chain, we say that the base @@ -712,7 +711,7 @@ void encoding2display_order( *displaying_order = encoding_order; // IDR frames are a special case; I honestly can't find the logic behind // why this is the right thing, but it seems to line up nicely in practice :-) - *pts_lag = TIMEBASE / frame_rate; + *pts_lag = TIMEBASE / MAX_FPS; } else if (((encoding_order_gop - 1) % ip_period) != 0) { /* B frames */ *frame_type = FRAME_B; *displaying_order = encoding_order - 1; @@ -873,7 +872,7 @@ static int process_cmdline(int argc, char *argv[]) } if (frame_bitrate == 0) - frame_bitrate = frame_width * frame_height * 12 * frame_rate / 50; + frame_bitrate = frame_width * frame_height * 12 * MAX_FPS / 50; if (coded_fn == NULL) { struct stat buf; @@ -1661,7 +1660,7 @@ void H264Encoder::save_codeddata(storage_task task) string data; - const int64_t global_delay = (ip_period - 1) * (TIMEBASE / frame_rate); // So we never get negative dts. + const int64_t global_delay = (ip_period - 1) * (TIMEBASE / MAX_FPS); // So we never get negative dts. va_status = vaMapBuffer(va_dpy, gl_surfaces[task.display_order % SURFACE_NUM].coded_buf, (void **)(&buf_list)); CHECK_VASTATUS(va_status, "vaMapBuffer"); @@ -1828,7 +1827,6 @@ static int print_input() if (rc_mode != -1) printf("INPUT: RateControl : %s\n", rc_to_string(rc_mode)); printf("INPUT: Resolution : %dx%dframes\n", frame_width, frame_height); - printf("INPUT: FrameRate : %d\n", frame_rate); printf("INPUT: Bitrate : %d\n", frame_bitrate); printf("INPUT: Slieces : %d\n", frame_slices); printf("INPUT: IntraPeriod : %d\n", intra_period); @@ -2059,7 +2057,7 @@ void H264Encoder::copy_thread_func() int64_t dts; if (pts_lag == -1) { assert(last_dts != -1); - dts = last_dts + (TIMEBASE / frame_rate); + dts = last_dts + (TIMEBASE / MAX_FPS); } else { dts = pts - pts_lag; } diff --git a/mixer.cpp b/mixer.cpp index 42a0252..936a070 100644 --- a/mixer.cpp +++ b/mixer.cpp @@ -212,6 +212,57 @@ void deinterleave_samples(const vector &in, vector *out_l, vector< } } +// Returns length of a frame with the given format, in TIMEBASE units. +int64_t find_frame_length(uint16_t video_format) +{ + if (video_format == 0x0800) { + // No video signal. These green pseudo-frames seem to come at about 30.13 Hz. + // It's a strange thing, but what can you do. + return TIMEBASE * 100 / 3013; + } + if ((video_format & 0xe800) != 0xe800) { + printf("Video format 0x%04x does not appear to be a video format. Assuming 60 Hz.\n", + video_format); + return TIMEBASE / 60; + } + + // 0x8 seems to be a flag about availability of deep color on the input, + // except when it's not (e.g. it's the only difference between NTSC 23.98 + // and PAL). Rather confusing. But we clear it here nevertheless, because + // usually it doesn't mean anything. + // + // We don't really handle interlaced formats at all yet. + uint16_t normalized_video_format = video_format & ~0xe808; + if (normalized_video_format == 0x0143) { // 720p50. + return TIMEBASE / 50; + } else if (normalized_video_format == 0x0103) { // 720p60. + return TIMEBASE / 60; + } else if (normalized_video_format == 0x0121) { // 720p59.94. + return TIMEBASE * 1001 / 60000; + } else if (normalized_video_format == 0x01c3 || // 1080p30. + normalized_video_format == 0x0003) { // 1080i60. + return TIMEBASE / 30; + } else if (normalized_video_format == 0x01e1 || // 1080p29.97. + normalized_video_format == 0x0021 || // 1080i59.94. + video_format == 0xe901 || // NTSC (480i59.94, I suppose). + video_format == 0xe9c1 || // Ditto. + video_format == 0xe801) { // Ditto. + return TIMEBASE * 1001 / 30000; + } else if (normalized_video_format == 0x0063 || // 1080p25. + normalized_video_format == 0x0043 || // 1080i50. + video_format == 0xe909) { // PAL (576i50, I suppose). + return TIMEBASE / 25; + } else if (normalized_video_format == 0x008e) { // 1080p24. + return TIMEBASE / 24; + } else if (normalized_video_format == 0x00a1) { // 1080p23.98. + return TIMEBASE * 1001 / 24000; + return TIMEBASE / 25; + } else { + printf("Unknown video format 0x%04x. Assuming 60 Hz.\n", video_format); + return TIMEBASE / 60; + } +} + } // namespace void Mixer::bm_frame(unsigned card_index, uint16_t timecode, @@ -220,6 +271,8 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, { CaptureCard *card = &cards[card_index]; + int64_t frame_length = find_frame_length(video_format); + if (audio_frame.len - audio_offset > 30000) { printf("Card %d: Dropping frame with implausible audio length (len=%d, offset=%d) [timecode=0x%04x video_len=%d video_offset=%d video_format=%x)\n", card_index, int(audio_frame.len), int(audio_offset), @@ -233,13 +286,12 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, return; } - int unwrapped_timecode = timecode; + int64_t local_pts = card->next_local_pts; int dropped_frames = 0; if (card->last_timecode != -1) { - unwrapped_timecode = unwrap_timecode(unwrapped_timecode, card->last_timecode); - dropped_frames = unwrapped_timecode - card->last_timecode - 1; + dropped_frames = unwrap_timecode(timecode, card->last_timecode) - card->last_timecode - 1; } - card->last_timecode = unwrapped_timecode; + card->last_timecode = timecode; // Convert the audio to stereo fp32 and add it. size_t num_samples = (audio_frame.len >= audio_offset) ? (audio_frame.len - audio_offset) / 8 / 3 : 0; @@ -251,22 +303,27 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, { unique_lock lock(card->audio_mutex); - int unwrapped_timecode = timecode; - if (dropped_frames > FPS * 2) { + if (dropped_frames > MAX_FPS * 2) { fprintf(stderr, "Card %d lost more than two seconds (or time code jumping around), resetting resampler\n", card_index); card->resampling_queue.reset(new ResamplingQueue(OUTPUT_FREQUENCY, OUTPUT_FREQUENCY, 2)); } else if (dropped_frames > 0) { - // Insert silence as needed. + // Insert silence as needed. (The number of samples could be nonintegral, + // but resampling will save us then.) fprintf(stderr, "Card %d dropped %d frame(s) (before timecode 0x%04x), inserting silence.\n", card_index, dropped_frames, timecode); vector silence; - silence.resize((OUTPUT_FREQUENCY / FPS) * 2); + silence.resize((OUTPUT_FREQUENCY * frame_length / TIMEBASE) * 2); for (int i = 0; i < dropped_frames; ++i) { - card->resampling_queue->add_input_samples((unwrapped_timecode - dropped_frames + i) / double(FPS), silence.data(), (OUTPUT_FREQUENCY / FPS)); + card->resampling_queue->add_input_samples(local_pts / double(TIMEBASE), silence.data(), silence.size() / 2); + // Note that if the format changed in the meantime, we have + // no way of detecting that; we just have to assume the frame length + // is always the same. + local_pts += frame_length; } } - card->resampling_queue->add_input_samples(unwrapped_timecode / double(FPS), audio.data(), num_samples); + card->resampling_queue->add_input_samples(local_pts / double(TIMEBASE), audio.data(), num_samples); + card->next_local_pts = local_pts + frame_length; } // Done with the audio, so release it. @@ -296,6 +353,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, unique_lock lock(bmusb_mutex); card->new_data_ready = true; card->new_frame = RefCountedFrame(FrameAllocator::Frame()); + card->new_frame_length = frame_length; card->new_data_ready_fence = nullptr; card->dropped_frames = dropped_frames; card->new_data_ready_changed.notify_all(); @@ -332,6 +390,7 @@ void Mixer::bm_frame(unsigned card_index, uint16_t timecode, unique_lock lock(bmusb_mutex); card->new_data_ready = true; card->new_frame = RefCountedFrame(video_frame); + card->new_frame_length = frame_length; card->new_data_ready_fence = fence; card->dropped_frames = dropped_frames; card->new_data_ready_changed.notify_all(); @@ -355,6 +414,7 @@ void Mixer::thread_func() while (!should_quit) { CaptureCard card_copy[MAX_CARDS]; + int num_samples[MAX_CARDS]; { unique_lock lock(bmusb_mutex); @@ -368,10 +428,15 @@ void Mixer::thread_func() card_copy[card_index].usb = card->usb; card_copy[card_index].new_data_ready = card->new_data_ready; card_copy[card_index].new_frame = card->new_frame; + card_copy[card_index].new_frame_length = card->new_frame_length; card_copy[card_index].new_data_ready_fence = card->new_data_ready_fence; card_copy[card_index].dropped_frames = card->dropped_frames; card->new_data_ready = false; card->new_data_ready_changed.notify_all(); + + int num_samples_times_timebase = OUTPUT_FREQUENCY * card->new_frame_length + card->fractional_samples; + num_samples[card_index] = num_samples_times_timebase / TIMEBASE; + card->fractional_samples = num_samples_times_timebase % TIMEBASE; } } @@ -380,13 +445,15 @@ void Mixer::thread_func() { // Signal to the audio thread to process this frame. unique_lock lock(audio_mutex); - audio_pts_queue.push(pts_int); - audio_pts_queue_changed.notify_one(); + audio_task_queue.push(AudioTask{pts_int, num_samples[0]}); + audio_task_queue_changed.notify_one(); } if (frame_num != card_copy[0].dropped_frames) { - // For dropped frames, increase the pts. + // For dropped frames, increase the pts. Note that if the format changed + // in the meantime, we have no way of detecting that; we just have to + // assume the frame length is always the same. ++dropped_frames; - pts_int += TIMEBASE / FPS; + pts_int += card_copy[0].new_frame_length; } } @@ -415,7 +482,7 @@ void Mixer::thread_func() // just increase the pts (skipping over this frame) and don't try to compute anything new. if (card_copy[0].new_frame->len == 0) { ++dropped_frames; - pts_int += TIMEBASE / FPS; + pts_int += card_copy[0].new_frame_length; continue; } @@ -480,7 +547,7 @@ void Mixer::thread_func() const int64_t av_delay = TIMEBASE / 10; // Corresponds to the fixed delay in resampling_queue.h. TODO: Make less hard-coded. h264_encoder->end_frame(fence, pts_int + av_delay, input_frames); ++frame; - pts_int += TIMEBASE / FPS; + pts_int += card_copy[0].new_frame_length; // The live frame just shows the RGBA texture we just rendered. // It owns rgba_tex now. @@ -539,28 +606,28 @@ void Mixer::thread_func() void Mixer::audio_thread_func() { while (!should_quit) { - int64_t frame_pts_int; + AudioTask task; { unique_lock lock(audio_mutex); - audio_pts_queue_changed.wait(lock, [this]{ return !audio_pts_queue.empty(); }); - frame_pts_int = audio_pts_queue.front(); - audio_pts_queue.pop(); + audio_task_queue_changed.wait(lock, [this]{ return !audio_task_queue.empty(); }); + task = audio_task_queue.front(); + audio_task_queue.pop(); } - process_audio_one_frame(frame_pts_int); + process_audio_one_frame(task.pts_int, task.num_samples); } } -void Mixer::process_audio_one_frame(int64_t frame_pts_int) +void Mixer::process_audio_one_frame(int64_t frame_pts_int, int num_samples) { vector samples_card; vector samples_out; for (unsigned card_index = 0; card_index < num_cards; ++card_index) { - samples_card.resize((OUTPUT_FREQUENCY / FPS) * 2); + samples_card.resize(num_samples * 2); { unique_lock lock(cards[card_index].audio_mutex); - if (!cards[card_index].resampling_queue->get_output_samples(double(frame_pts_int) / TIMEBASE, &samples_card[0], OUTPUT_FREQUENCY / FPS)) { + if (!cards[card_index].resampling_queue->get_output_samples(double(frame_pts_int) / TIMEBASE, &samples_card[0], num_samples)) { printf("Card %d reported previous underrun.\n", card_index); } } diff --git a/mixer.h b/mixer.h index 8f74cc5..6cd9126 100644 --- a/mixer.h +++ b/mixer.h @@ -177,7 +177,7 @@ private: void place_rectangle(movit::Effect *resample_effect, movit::Effect *padding_effect, float x0, float y0, float x1, float y1); void thread_func(); void audio_thread_func(); - void process_audio_one_frame(int64_t frame_pts_int); + void process_audio_one_frame(int64_t frame_pts_int, int num_samples); void subsample_chroma(GLuint src_tex, GLuint dst_dst); void release_display_frame(DisplayFrame *frame); double pts() { return double(pts_int) / TIMEBASE; } @@ -209,13 +209,19 @@ private: bool new_data_ready = false; // Whether new_frame contains anything. bool should_quit = false; RefCountedFrame new_frame; + int64_t new_frame_length; // In TIMEBASE units. GLsync new_data_ready_fence; // Whether new_frame is ready for rendering. std::condition_variable new_data_ready_changed; // Set whenever new_data_ready is changed. unsigned dropped_frames = 0; // Before new_frame. + // Accumulated errors in number of 1/TIMEBASE samples. If OUTPUT_FREQUENCY divided by + // frame rate is integer, will always stay zero. + unsigned fractional_samples = 0; + std::mutex audio_mutex; std::unique_ptr resampling_queue; // Under audio_mutex. int last_timecode = -1; // Unwrapped. + int64_t next_local_pts = 0; // Beginning of next frame, in TIMEBASE units. }; CaptureCard cards[MAX_CARDS]; // protected by @@ -268,9 +274,13 @@ private: std::unique_ptr alsa; + struct AudioTask { + int64_t pts_int; + int num_samples; + }; std::mutex audio_mutex; - std::condition_variable audio_pts_queue_changed; - std::queue audio_pts_queue; + std::condition_variable audio_task_queue_changed; + std::queue audio_task_queue; // Under audio_mutex. }; extern Mixer *global_mixer; -- 2.39.2