[0.1.10] Recording toolbar: a System sound button beside Mic

- System sound: on / off (red while on) turns what the computer plays on and off while
  recording, as Mic does the microphone; its state is saved as system_audio in [video].
- modules/Screen_Record: the microphone's switch is now one for either source:
  record_set_audio, record_audio_on and record_audio_failed take an Audio_Source_Kind, and the
  audio thread opens and closes each as it is turned on and off.
- record-probe -system-toggle. With -sync-test, system sound on at 3 s and off at 7 s keeps only
  the beeps at 3 and 5 s (8-12 ms from their flashes), and the track runs the whole recording.
This commit is contained in:
Musa Mahmood 2026-10-10 08:20:13 +01:00
parent 3f5d1ea124
commit 4acde4d7a6
9 changed files with 106 additions and 87 deletions

View File

@ -188,12 +188,12 @@ register the key, the Event Log says so; choose another, such as `Ctrl-Shift-S
- Escape or a right click cancels. - Escape or a right click cancels.
3. **Recording starts** at once, at up to 30 frames a second, cursor included. A frame is written 3. **Recording starts** at once, at up to 30 frames a second, cursor included. A frame is written
only when the screen changes, so a still screen costs almost nothing. **Sound:** what the only when the screen changes, so a still screen costs almost nothing. **Sound:** what the
computer plays is recorded with it. The microphone is off until you turn it on with the computer plays is recorded with it, and the microphone once you turn it on. The toolbar's
toolbar's **Mic** button, which works mid-recording. The button's state is kept for the next **System sound** and **Mic** buttons turn each on and off, mid-recording too, and their state is
recording. kept for the next recording.
- **A red border** is drawn just outside the region. - **A red border** is drawn just outside the region.
- **A toolbar** beside it shows the time, the size and any frames dropped, with **Mic** (on or - **A toolbar** beside it shows the time, the size and any frames dropped, with **System sound**
off), **Pause** (the time paused is left out of the video), **Stop**, and **Discard**, which asks and **Mic** (each on or off), **Pause** (the time paused is left out of the video), **Stop**, and **Discard**, which asks
first. first.
- **Neither is in the recording.** Neither takes the focus from the window being recorded. - **Neither is in the recording.** Neither takes the focus from the window being recorded.
4. **Ctrl+PrintScreen again**, or Stop, stops it. The video goes into **Screen Captures** beside the stills, 4. **Ctrl+PrintScreen again**, or Stop, stops it. The video goes into **Screen Captures** beside the stills,
@ -213,7 +213,8 @@ register the key, the Event Log says so; choose another, such as `Ctrl-Shift-S
- **Settings:** `[video]` in the settings file: - **Settings:** `[video]` in the settings file:
- `fps` (30) and `cursor` (true). - `fps` (30) and `cursor` (true).
- `capture`: `auto` (Desktop Duplication, else Windows.Graphics.Capture), `dxgi` or `wgc`. - `capture`: `auto` (Desktop Duplication, else Windows.Graphics.Capture), `dxgi` or `wgc`.
- `system_audio` (true): what the computer plays, from the default output device. - `system_audio` (true): what the computer plays, from the default output device. The toolbar's
System sound button sets it.
- `microphone` (false): the default input device. The toolbar's Mic button sets it. - `microphone` (false): the default input device. The toolbar's Mic button sets it.
- Sound is AAC at 160 kb/s. A device unplugged mid-recording is picked up again from the new default. - Sound is AAC at 160 kb/s. A device unplugged mid-recording is picked up again from the new default.
- **PrintScreen while recording** takes the whole screen under the cursor at once, without freezing - **PrintScreen while recording** takes the whole screen under the cursor at once, without freezing

View File

@ -29,7 +29,7 @@ APP_NAME :: "mcap";
APP_FOLDER_NAME :: "mcap"; APP_FOLDER_NAME :: "mcap";
GIT_BRANCH :: "check"; GIT_BRANCH :: "check";
GIT_HASH :: "-"; GIT_HASH :: "-";
VERSION :: "0.1.9"; VERSION :: "0.1.10";
BUILD_DATE :: "-"; BUILD_DATE :: "-";
DEBUG :: true; DEBUG :: true;
COMPILE_TESTS :: true; COMPILE_TESTS :: true;

View File

@ -8,7 +8,7 @@ APP_NAME :: "mcap";
APP_FOLDER_NAME :: "mcap"; APP_FOLDER_NAME :: "mcap";
APP_BUNDLE_IDENTIFIER :: "com.example.mcap"; APP_BUNDLE_IDENTIFIER :: "com.example.mcap";
VERSION :: "0.1.9"; VERSION :: "0.1.10";
// Where everything goes: bin/, or bin-headless/ for builds made with no_crash_dialog, so automated // Where everything goes: bin/, or bin-headless/ for builds made with no_crash_dialog, so automated
// builds never write over the ones you run by hand. // builds never write over the ones you run by hand.
build_directory := "bin"; build_directory := "bin";

View File

@ -16,9 +16,9 @@
// Sound (system_audio, microphone) has a thread of its own (audio_source.jai's WASAPI): each packet is // Sound (system_audio, microphone) has a thread of its own (audio_source.jai's WASAPI): each packet is
// put on the same timeline as the frames, by when it was heard, less the same pauses, into one mix, // put on the same timeline as the frames, by when it was heard, less the same pauses, into one mix,
// and an AAC frame of it is encoded once every source has had its say up to there. A source that sends // and an AAC frame of it is encoded once every source has had its say up to there. A source that sends
// nothing (the computer playing nothing) is silence. The microphone can be turned on and off while // nothing (the computer playing nothing) is silence. Either source can be turned on and off while
// recording (record_set_microphone); with audio_track, the MP4 has a sound track even when no source // recording (record_set_audio); with audio_track, the MP4 has a sound track even when no source is on
// is on at the start, so it can be. // at the start, so one can be.
// //
// recorder: Recorder; // recorder: Recorder;
// ok, error, fall_back := record_start(*recorder, .{path = "out.mp4", screen = 0}); // ok, error, fall_back := record_start(*recorder, .{path = "out.mp4", screen = 0});
@ -74,8 +74,8 @@ Record_Options :: struct {
legacy_duplicate: bool; // the plain DuplicateOutput, not DuplicateOutput1: for record-probe legacy_duplicate: bool; // the plain DuplicateOutput, not DuplicateOutput1: for record-probe
testing: Record_Testing; testing: Record_Testing;
system_audio: bool; // what the computer plays (the default output device) system_audio: bool; // what the computer plays (the default output device)
microphone: bool; // the default input device, at the start: record_set_microphone turns it on and off microphone: bool; // the default input device; both at the start: record_set_audio turns them on and off
audio_track: bool; // a sound track whatever is on at the start, so the microphone can be turned on later audio_track: bool; // a sound track whatever is on at the start, so a source can be turned on later
} }
Record_Stats :: struct { Record_Stats :: struct {
@ -118,8 +118,8 @@ Recorder :: struct {
audio_go: Semaphore; // the encoder is open: the audio thread may write audio_go: Semaphore; // the encoder is open: the audio thread may write
audio_sources: [2] Audio_Source; audio_sources: [2] Audio_Source;
audio_open: bool; // any source opened, or audio_track: the MP4 has sound audio_open: bool; // any source opened, or audio_track: the MP4 has sound
microphone_on: bool; // the microphone is wanted: record_set_microphone; cleared when it cannot be opened audio_on: [2] bool; // each source is wanted (by Audio_Source_Kind): record_set_audio; cleared when it cannot be opened
microphone_failed: bool; // it could not be opened after being turned on: record_microphone_failed says once audio_failed: [2] bool; // it could not be opened after being turned on: record_audio_failed says once
zero_ticks: s64; // the recording's time 0, for frames and sound alike zero_ticks: s64; // the recording's time 0, for frames and sound alike
paused_ticks: s64; // time paused so far, for the capture and audio threads; set by record_pause paused_ticks: s64; // time paused so far, for the capture and audio threads; set by record_pause
pause_began: s64; pause_began: s64;
@ -214,7 +214,7 @@ record_start :: (r: *Recorder, options: Record_Options) -> ok: bool, error: stri
// The sound's sources are opened on their own thread (COM, for WASAPI), before the encoder, which // The sound's sources are opened on their own thread (COM, for WASAPI), before the encoder, which
// has a sound stream only when one opened. // has a sound stream only when one opened.
r.microphone_on = r.options.microphone; r.audio_on = .[r.options.system_audio, r.options.microphone];
if r.options.system_audio || r.options.microphone || r.options.audio_track { if r.options.system_audio || r.options.microphone || r.options.audio_track {
init(*r.audio_opened); init(*r.audio_opened);
init(*r.audio_go); init(*r.audio_go);
@ -276,22 +276,22 @@ record_pause :: (r: *Recorder, pause: bool) {
} }
} }
// Turns the microphone on or off while recording. It is opened only while on, so Windows shows it in // Turns a source of sound on or off while recording. It is opened only while on, so Windows shows the
// use only then. False when the recording has no sound track to put it in. // microphone in use only then. False when the recording has no sound track to put it in.
record_set_microphone :: (r: *Recorder, on: bool) -> bool { record_set_audio :: (r: *Recorder, kind: Audio_Source_Kind, on: bool) -> bool {
if !r.running || !r.audio_open return false; if !r.running || !r.audio_open return false;
atomic_swap(*r.microphone_on, on); atomic_swap(*r.audio_on[kind], on);
return true; return true;
} }
record_microphone :: (r: *Recorder) -> bool { record_audio_on :: (r: *Recorder, kind: Audio_Source_Kind) -> bool {
return r.running && atomic_read(*r.microphone_on); return r.running && atomic_read(*r.audio_on[kind]);
} }
// Whether the microphone could not be opened when turned on (it is off again then), and why; once. // Whether a source could not be opened when turned on (it is off again then), and why; once.
record_microphone_failed :: (r: *Recorder) -> failed: bool, error: string { record_audio_failed :: (r: *Recorder, kind: Audio_Source_Kind) -> failed: bool, error: string {
if !r.running || !atomic_read(*r.microphone_failed) return false, ""; if !r.running || !atomic_read(*r.audio_failed[kind]) return false, "";
atomic_swap(*r.microphone_failed, false); atomic_swap(*r.audio_failed[kind], false);
lock(*r.mutex); lock(*r.mutex);
defer unlock(*r.mutex); defer unlock(*r.mutex);
return true, copy_string(r.stats.audio_errors,, temp); return true, copy_string(r.stats.audio_errors,, temp);
@ -617,7 +617,7 @@ record_audio_proc :: (thread: *Thread) -> s64 {
if ok { r.audio_open = true; note(*names, s.name, *r.stats.audio); } if ok { r.audio_open = true; note(*names, s.name, *r.stats.audio); }
else { audio_close(s); note(*errors, tprint("%: %", what, error), *r.stats.audio_errors); log_error("[record] No %: %", what, error); } else { audio_close(s); note(*errors, tprint("%: %", what, error), *r.stats.audio_errors); log_error("[record] No %: %", what, error); }
} }
if !r.audio_sources[1].open atomic_swap(*r.microphone_on, false); for r.audio_sources if !it.open atomic_swap(*r.audio_on[it_index], false);
if r.options.audio_track r.audio_open = true; if r.options.audio_track r.audio_open = true;
signal(*r.audio_opened); signal(*r.audio_opened);
wait_for(*r.audio_go); wait_for(*r.audio_go);
@ -631,28 +631,30 @@ record_audio_proc :: (thread: *Thread) -> s64 {
defer array_free(mix.samples); defer array_free(mix.samples);
while true { while true {
stopping := atomic_read(*r.stopping); stopping := atomic_read(*r.stopping);
// The microphone, turned on or off from the toolbar: opened only while on. // Each source, turned on or off from the toolbar: opened only while on.
mic := *r.audio_sources[1]; for * source, index: r.audio_sources {
mic_wanted := atomic_read(*r.microphone_on) && !stopping; on := atomic_read(*r.audio_on[index]) && !stopping;
if mic_wanted && !mic.open && !wanted[1] { what := ifx kinds[index] == .SYSTEM then "System sound" else "Microphone";
ok, error := audio_open(mic, .MICROPHONE); if on && !source.open && !wanted[index] {
if ok { ok, error := audio_open(source, kinds[index]);
log("[record] Microphone on: %.", mic.name); if ok {
note(*names, mic.name, *r.stats.audio); log("[record] % on: %.", what, source.name);
mix.started[1] = false; note(*names, source.name, *r.stats.audio);
} else { mix.started[index] = false;
audio_close(mic); } else {
log_error("[record] The microphone could not be opened: %", error); audio_close(source);
note(*errors, tprint("microphone: %", error), *r.stats.audio_errors); log_error("[record] % could not be opened: %", what, error);
atomic_swap(*r.microphone_on, false); note(*errors, tprint("%: %", to_lower_copy(what,, temp), error), *r.stats.audio_errors);
atomic_swap(*r.microphone_failed, true); atomic_swap(*r.audio_on[index], false);
atomic_swap(*r.audio_failed[index], true);
}
} else if !on && source.open {
log("[record] % off.", what);
audio_close(source);
source.lost_at = 0;
} }
} else if !mic_wanted && mic.open { wanted[index] = source.open || (wanted[index] && on);
log("[record] Microphone off.");
audio_close(mic);
mic.lost_at = 0;
} }
wanted[1] = mic.open || (wanted[1] && mic_wanted);
for * source, index: r.audio_sources { for * source, index: r.audio_sources {
if !wanted[index] continue; if !wanted[index] continue;

View File

@ -11,8 +11,8 @@
// time, Pause, Stop and Discard (app/screen_record_win32.jai); both are kept out of the recording. // time, Pause, Stop and Discard (app/screen_record_win32.jai); both are kept out of the recording.
// Time paused is left out of the video. With sound: what the computer plays and the microphone, as // Time paused is left out of the video. With sound: what the computer plays and the microphone, as
// [video] in the settings file says, as are the frame rate, the cursor and how the screen is taken. // [video] in the settings file says, as are the frame rate, the cursor and how the screen is taken.
// The toolbar's Mic button turns the microphone on and off while recording, and is kept in [video] // The toolbar's System sound and Mic buttons turn each on and off while recording, and are kept in
// for the next: every recording has a sound track for it, silent while nothing is on. // [video] for the next: every recording has a sound track for them, silent while nothing is on.
// //
// A recording that stops by itself (the screen went away, the encoder failed) keeps what it has, and // A recording that stops by itself (the screen went away, the encoder failed) keeps what it has, and
// so does quitting while recording. One left in the temporary folder by a crash is offered at the next // so does quitting while recording. One left in the temporary folder by a crash is offered at the next
@ -52,21 +52,25 @@ screen_record_frame :: () {
screen_record_stop(); screen_record_stop();
return; return;
} }
failed_mic, mic_error := record_microphone_failed(*screen_record.recorder); for kind: Audio_Source_Kind.[.SYSTEM, .MICROPHONE] {
if failed_mic { failed, why := record_audio_failed(*screen_record.recorder, kind);
toast_show(.Error, "The microphone could not be turned on: %", mic_error); if !failed continue;
screen_record_remember_microphone(false); toast_show(.Error, "% could not be turned on: %", screen_record_audio_name(kind), why);
screen_record_remember_audio(kind, false);
record_ui_refresh(); record_ui_refresh();
} }
if record_ui_take_ask() == { ask := record_ui_take_ask();
case .MICROPHONE; if ask == .SYSTEM_SOUND || ask == .MICROPHONE {
on := !record_microphone(*screen_record.recorder); kind: Audio_Source_Kind = ifx ask == .SYSTEM_SOUND then .SYSTEM else .MICROPHONE;
if record_set_microphone(*screen_record.recorder, on) { on := !record_audio_on(*screen_record.recorder, kind);
log("[record] Microphone turned %.", ifx on then "on" else "off"); if record_set_audio(*screen_record.recorder, kind, on) {
toast_show(.Success, "Microphone %", ifx on then "on" else "off"); log("[record] % turned %.", screen_record_audio_name(kind), ifx on then "on" else "off");
screen_record_remember_microphone(on); toast_show(.Success, "% %", screen_record_audio_name(kind), ifx on then "on" else "off");
} screen_record_remember_audio(kind, on);
record_ui_refresh(); }
record_ui_refresh();
}
if ask == {
case .PAUSE; case .PAUSE;
pause := !record_paused(*screen_record.recorder); pause := !record_paused(*screen_record.recorder);
record_pause(*screen_record.recorder, pause); record_pause(*screen_record.recorder, pause);
@ -222,7 +226,7 @@ screen_record_start :: (immediate := false) {
options.cursor = app_user_settings.video_cursor; options.cursor = app_user_settings.video_cursor;
options.system_audio = app_user_settings.video_system_audio; options.system_audio = app_user_settings.video_system_audio;
options.microphone = app_user_settings.video_microphone; options.microphone = app_user_settings.video_microphone;
options.audio_track = true; // so the Mic button can turn the microphone on mid-recording options.audio_track = true; // so the toolbar can turn a source on mid-recording
if app_user_settings.video_capture == { if app_user_settings.video_capture == {
case "dxgi"; options.capture = .DXGI; case "dxgi"; options.capture = .DXGI;
case "wgc"; options.capture = .WGC; case "wgc"; options.capture = .WGC;
@ -253,13 +257,18 @@ screen_record_start :: (immediate := false) {
record_ui_open(options.region, shot.whole_screen, stats.width, stats.height); record_ui_open(options.region, shot.whole_screen, stats.width, stats.height);
} }
// The Mic button's state, into [video] for the next recording. // A sound button's state, into [video] for the next recording.
screen_record_remember_microphone :: (on: bool) { screen_record_remember_audio :: (kind: Audio_Source_Kind, on: bool) {
if app_user_settings.video_microphone == on return; setting := ifx kind == .SYSTEM then *app_user_settings.video_system_audio else *app_user_settings.video_microphone;
app_user_settings.video_microphone = on; if setting.* == on return;
setting.* = on;
app_save_user_settings_file(); app_save_user_settings_file();
} }
screen_record_audio_name :: (kind: Audio_Source_Kind) -> string {
return ifx kind == .SYSTEM then "System sound" else "The microphone";
}
// Stops, and deletes what was recorded (the toolbar's Discard, which asked first). // Stops, and deletes what was recorded (the toolbar's Discard, which asked first).
screen_record_discard :: () { screen_record_discard :: () {
if !screen_record.recording return; if !screen_record.recording return;

View File

@ -1,5 +1,6 @@
// While recording (app/screen_record.jai), on Windows: a thin red border just outside the region, and a // While recording (app/screen_record.jai), on Windows: a thin red border just outside the region, and a
// toolbar beside it with the time, the size, frames dropped, and Mic, Pause, Stop and Discard. Both are // toolbar beside it with the time, the size, frames dropped, and System sound, Mic, Pause, Stop and
// Discard. Both are
// plain GDI windows, as the overlay is, so they work while mcap is hidden in the tray, and both are // plain GDI windows, as the overlay is, so they work while mcap is hidden in the tray, and both are
// kept out of every capture (WDA_EXCLUDEFROMCAPTURE), the recording's own included. The border is // kept out of every capture (WDA_EXCLUDEFROMCAPTURE), the recording's own included. The border is
// outside the region anyway, so it could never be in the video. // outside the region anyway, so it could never be in the video.
@ -9,7 +10,7 @@
// message, as Stop and Discard close the toolbar. Discard asks first, in the toolbar itself: a // message, as Stop and Discard close the toolbar. Discard asks first, in the toolbar itself: a
// message box would be in the recording. // message box would be in the recording.
Record_UI_Ask :: enum u8 { NONE; MICROPHONE; PAUSE; STOP; DISCARD; } Record_UI_Ask :: enum u8 { NONE; SYSTEM_SOUND; MICROPHONE; PAUSE; STOP; DISCARD; }
record_ui: struct { record_ui: struct {
border, toolbar: HWND; border, toolbar: HWND;
@ -19,7 +20,7 @@ record_ui: struct {
asked: Record_UI_Ask; // a button pressed, for screen_record_frame asked: Record_UI_Ask; // a button pressed, for screen_record_frame
confirming: bool; // Discard pressed once: "Discard it? Yes No" confirming: bool; // Discard pressed once: "Discard it? Yes No"
hovered: s32 = -1; // the button under the mouse hovered: s32 = -1; // the button under the mouse
buttons: [4] Record_UI_Button; buttons: [5] Record_UI_Button;
button_count: s32; button_count: s32;
started: Apollo_Time; started: Apollo_Time;
size_text: string; // "1920 × 1080"; heap size_text: string; // "1920 × 1080"; heap
@ -170,11 +171,13 @@ record_toolbar_buttons :: () -> [] Record_UI_Button {
record_ui.buttons[1] = .{label = "No", confirm = true}; record_ui.buttons[1] = .{label = "No", confirm = true};
record_ui.button_count = 2; record_ui.button_count = 2;
} else { } else {
record_ui.buttons[0] = .{label = ifx record_microphone(*screen_record.recorder) then "Mic: on" else "Mic: off", ask = .MICROPHONE}; recorder := *screen_record.recorder;
record_ui.buttons[1] = .{label = ifx record_paused(*screen_record.recorder) then "Resume" else "Pause", ask = .PAUSE}; record_ui.buttons[0] = .{label = ifx record_audio_on(recorder, .SYSTEM) then "System sound: on" else "System sound: off", ask = .SYSTEM_SOUND};
record_ui.buttons[2] = .{label = "Stop", ask = .STOP}; record_ui.buttons[1] = .{label = ifx record_audio_on(recorder, .MICROPHONE) then "Mic: on" else "Mic: off", ask = .MICROPHONE};
record_ui.buttons[3] = .{label = "Discard", confirm = true}; record_ui.buttons[2] = .{label = ifx record_paused(recorder) then "Resume" else "Pause", ask = .PAUSE};
record_ui.button_count = 4; record_ui.buttons[3] = .{label = "Stop", ask = .STOP};
record_ui.buttons[4] = .{label = "Discard", confirm = true};
record_ui.button_count = 5;
} }
return array_view(record_ui.buttons, 0, record_ui.button_count); return array_view(record_ui.buttons, 0, record_ui.button_count);
} }
@ -197,7 +200,7 @@ record_toolbar_size :: () -> width: s32, height: s32 {
padding, gap := record_ui_px(10), record_ui_px(8); padding, gap := record_ui_px(10), record_ui_px(8);
status_width := text_width(dc, tprint("PAUSED 00:00:00 %", record_ui.size_text)); status_width := text_width(dc, tprint("PAUSED 00:00:00 %", record_ui.size_text));
dropped_width := text_width(dc, "999 dropped, screen lost 9 times"); dropped_width := text_width(dc, "999 dropped, screen lost 9 times");
buttons_width := text_width(dc, "Mic: off") + text_width(dc, "Resume") + text_width(dc, "Stop") + text_width(dc, "Yes, discard") + 4 * 2 * padding + 3 * gap; buttons_width := text_width(dc, "System sound: off") + text_width(dc, "Mic: off") + text_width(dc, "Resume") + text_width(dc, "Stop") + text_width(dc, "Yes, discard") + 5 * 2 * padding + 4 * gap;
height := record_ui_px(34); height := record_ui_px(34);
width := padding + record_ui_px(12) + gap + status_width + gap + dropped_width + gap + buttons_width + padding; width := padding + record_ui_px(12) + gap + status_width + gap + dropped_width + gap + buttons_width + padding;
return width, height; return width, height;
@ -314,7 +317,8 @@ record_toolbar_paint :: (hwnd: HWND) {
label_width := text_width(dc, it.label); label_width := text_width(dc, it.label);
r := RECT.{right - label_width - 2 * padding, (height - button_height) / 2, right, (height - button_height) / 2 + button_height}; r := RECT.{right - label_width - 2 * padding, (height - button_height) / 2, right, (height - button_height) / 2 + button_height};
record_ui.buttons[it_index].rect = r; record_ui.buttons[it_index].rect = r;
lit := it.ask == .MICROPHONE && record_microphone(*screen_record.recorder); // on: in the record red // A sound that is on, in the record red.
lit := (it.ask == .SYSTEM_SOUND && record_audio_on(*screen_record.recorder, .SYSTEM)) || (it.ask == .MICROPHONE && record_audio_on(*screen_record.recorder, .MICROPHONE));
fill(r, ifx lit then cast(u32) RECORD_UI_RED else ifx it_index == record_ui.hovered then cast(u32) RECORD_UI_HOVER else cast(u32) RECORD_UI_BUTTON); fill(r, ifx lit then cast(u32) RECORD_UI_RED else ifx it_index == record_ui.hovered then cast(u32) RECORD_UI_HOVER else cast(u32) RECORD_UI_BUTTON);
SetTextColor(dc, ifx it.ask == .DISCARD then cast(u32) RECORD_UI_AMBER else cast(u32) RECORD_UI_TEXT); SetTextColor(dc, ifx it.ask == .DISCARD then cast(u32) RECORD_UI_AMBER else cast(u32) RECORD_UI_TEXT);
wide := utf8_to_wide(it.label,, temp); wide := utf8_to_wide(it.label,, temp);

View File

@ -670,8 +670,7 @@ app_save_user_settings_file :: () -> bool {
if seen.event_log app_user_settings.event_log_capacity = on_disk.event_log_capacity; if seen.event_log app_user_settings.event_log_capacity = on_disk.event_log_capacity;
if seen.video_fps app_user_settings.video_fps = on_disk.video_fps; if seen.video_fps app_user_settings.video_fps = on_disk.video_fps;
if seen.video_cursor app_user_settings.video_cursor = on_disk.video_cursor; if seen.video_cursor app_user_settings.video_cursor = on_disk.video_cursor;
if seen.video_system_audio app_user_settings.video_system_audio = on_disk.video_system_audio; // video_system_audio and video_microphone are the app's: the recording toolbar's buttons set them.
// video_microphone is the app's: the recording toolbar's Mic button sets it.
if seen.video_capture { if seen.video_capture {
free(app_user_settings.video_capture,, context.default_allocator); free(app_user_settings.video_capture,, context.default_allocator);
app_user_settings.video_capture = copy_string(on_disk.video_capture,, context.default_allocator); app_user_settings.video_capture = copy_string(on_disk.video_capture,, context.default_allocator);

View File

@ -22,7 +22,8 @@
// frame: for measuring throughput. // frame: for measuring throughput.
// -remux IN OUT a recording's fragmented MP4 made a regular one, as the app does, and nothing else. // -remux IN OUT a recording's fragmented MP4 made a regular one, as the app does, and nothing else.
// -audio WHICH records sound too: system (what the computer plays), mic, or both. // -audio WHICH records sound too: system (what the computer plays), mic, or both.
// -mic-toggle with -audio system: the microphone turned on 3 s in, and off at 7 s, as the toolbar does. // -mic-toggle the microphone turned on 3 s in, and off at 7 s, as the toolbar does.
// -system-toggle likewise for what the computer plays.
// -sync-test every 2 s a white square flashes on the region and a 1 kHz beep plays; then the // -sync-test every 2 s a white square flashes on the region and a 1 kHz beep plays; then the
// flashes and beeps are found in the file and their offsets given, and how they drift. // flashes and beeps are found in the file and their offsets given, and how they drift.
// -check FILE reads FILE back with FFmpeg and says what it holds, and nothing else: for a // -check FILE reads FILE back with FFmpeg and says what it holds, and nothing else: for a
@ -47,6 +48,7 @@ main :: () {
pause_seconds: float64; pause_seconds: float64;
sync_test := false; sync_test := false;
mic_toggle := false; mic_toggle := false;
system_toggle := false;
i := 1; i := 1;
number :: (index: s64) -> s32 #expand { return cast(s32) string_to_int(`args[index]); } number :: (index: s64) -> s32 #expand { return cast(s32) string_to_int(`args[index]); }
while i < args.count { while i < args.count {
@ -77,6 +79,7 @@ main :: () {
if a == "-lose" { options.testing |= .LOSE_DUPLICATION; i += 1; continue; } if a == "-lose" { options.testing |= .LOSE_DUPLICATION; i += 1; continue; }
if a == "-sync-test" { sync_test = true; i += 1; continue; } if a == "-sync-test" { sync_test = true; i += 1; continue; }
if a == "-mic-toggle" { mic_toggle = true; options.audio_track = true; i += 1; continue; } if a == "-mic-toggle" { mic_toggle = true; options.audio_track = true; i += 1; continue; }
if a == "-system-toggle" { system_toggle = true; options.audio_track = true; i += 1; continue; }
if a == "-audio" && more >= 1 { if a == "-audio" && more >= 1 {
which := args[i + 1]; which := args[i + 1];
options.system_audio = which == "system" || which == "both"; options.system_audio = which == "system" || which == "both";
@ -197,15 +200,16 @@ main :: () {
print(" % s: % captured, % sent (% repeats), % written, % dropped, % lost, % KB\n", print(" % s: % captured, % sent (% repeats), % written, % dropped, % lost, % KB\n",
FF(to_float64_seconds(last_print - start), 0), s.captured, s.emitted, s.repeats, s.written, s.dropped, s.lost, s.bytes / 1024); FF(to_float64_seconds(last_print - start), 0), s.captured, s.emitted, s.repeats, s.written, s.dropped, s.lost, s.bytes / 1024);
} }
if mic_toggle { for kind: Audio_Source_Kind.[.SYSTEM, .MICROPHONE] {
if !(ifx kind == .SYSTEM then system_toggle else mic_toggle) continue;
elapsed := to_float64_seconds(current_time_monotonic() - start); elapsed := to_float64_seconds(current_time_monotonic() - start);
should := elapsed >= 3 && elapsed < 7; should := elapsed >= 3 && elapsed < 7;
if should != record_microphone(*recorder) && (elapsed < 3.5 || (elapsed >= 7 && elapsed < 7.5)) { if should != record_audio_on(*recorder, kind) && (elapsed < 3.5 || (elapsed >= 7 && elapsed < 7.5)) {
record_set_microphone(*recorder, should); record_set_audio(*recorder, kind, should);
print(" % s: microphone %\n", FF(elapsed, 1), ifx should then "on" else "off"); print(" % s: % %\n", FF(elapsed, 1), kind, ifx should then "on" else "off");
} }
failed, why := record_microphone_failed(*recorder); failed, why := record_audio_failed(*recorder, kind);
if failed print(" microphone failed: %\n", why); if failed print(" % failed: %\n", kind, why);
} }
if flash { // a flash and a beep together every 2 s, the flash for 200 ms if flash { // a flash and a beep together every 2 s, the flash for 200 ms
elapsed := to_float64_seconds(current_time_monotonic() - start); elapsed := to_float64_seconds(current_time_monotonic() - start);

View File

@ -506,7 +506,7 @@ Captured_Frame :: struct {
<li>An MP4 can't gain a sound stream once its header is written, so the app's recordings always have one (<code>audio_track</code>), silent while nothing is on.</li> <li>An MP4 can't gain a sound stream once its header is written, so the app's recordings always have one (<code>audio_track</code>), silent while nothing is on.</li>
<li>Checked with <code>record-probe -mic-toggle</code>, which turns the microphone on at 3 s and off at 7 s. With system sound, and with nothing else on, the sound track runs the whole recording without a break.</li> <li>Checked with <code>record-probe -mic-toggle</code>, which turns the microphone on at 3 s and off at 7 s. With system sound, and with nothing else on, the sound track runs the whole recording without a break.</li>
</ul> </ul>
What the computer plays is still chosen only in <code>[video]</code>.</li> In mcap 0.1.10, <strong>System sound: on / off</strong> joined it and works the same way, saved as <code>system_audio</code>. With <code>record-probe -system-toggle -sync-test</code> (system sound on at 3 s, off at 7 s), only the beeps at 3 and 5 s are in the file, 8 to 12 ms from their flashes, and the track runs the whole recording.</li>
<li><strong>A lost device</strong> (a headset unplugged, the default changed) is opened again from the new default once a second.</li> <li><strong>A lost device</strong> (a headset unplugged, the default changed) is opened again from the new default once a second.</li>
</ul></div> </ul></div>
</div> </div>