diff options
Diffstat (limited to 'video.c')
-rw-r--r-- | video.c | 68 |
1 files changed, 56 insertions, 12 deletions
@@ -1,24 +1,31 @@ #include "video.h" -#include "camera.h" +#include <stdatomic.h> +#include <SDL.h> #include <libavcodec/avcodec.h> #include <libavformat/avformat.h> +#include <pulse/pulseaudio.h> +#include <pulse/simple.h> #include "util.h" +#include "camera.h" struct VideoContext { bool recording; double start_time; + double last_frame_time; AVFormatContext *avf_context; AVCodecContext *video_encoder; AVFrame *video_frame; AVPacket *av_packet; AVStream *video_stream; - int64_t video_pts; + int64_t next_video_pts; + pa_simple *pulseaudio; }; VideoContext *video_init(void) { - return calloc(1, sizeof(VideoContext)); + VideoContext *ctx = calloc(1, sizeof(VideoContext)); + return ctx; } bool video_start(VideoContext *ctx, const char *filename, int32_t width, int32_t height, int fps, int quality) { @@ -93,9 +100,23 @@ bool video_start(VideoContext *ctx, const char *filename, int32_t width, int32_t fprintf(stderr, "error: avformat_write_header: %s\n", av_err2str(err)); return false; } + pa_sample_spec audio_format = { + .format = PA_SAMPLE_S16NE, + .channels = 2, + .rate = 44100 + }; + // NOTE: SDL2 audio capture is currently broken + // https://github.com/libsdl-org/SDL/issues/9706 + // once SDL3 becomes commonplace enough, we can switch to that for capturing audio. + ctx->pulseaudio = pa_simple_new(NULL, "camlet", PA_STREAM_RECORD, NULL, + "microphone", &audio_format, NULL, NULL, &err); + if (!ctx->pulseaudio) { + fprintf(stderr, "%s", pa_strerror(err)); + } + pa_simple_read(ctx->pulseaudio, (char[1]){0}, 1, &err); ctx->recording = true; - ctx->video_pts = 0; - ctx->start_time = get_time_double(); + ctx->next_video_pts = 0; + ctx->start_time = ctx->last_frame_time = get_time_double(); return true; } @@ -127,21 +148,40 @@ static bool write_frame(VideoContext *ctx, AVCodecContext *encoder, AVStream *st } bool video_submit_frame(VideoContext *ctx, Camera *camera) { - if (!ctx || !camera) return false; - int64_t next_pts = ctx->video_pts; - int64_t curr_pts = (int64_t)((get_time_double() - ctx->start_time) + if (!ctx || !camera || !ctx->recording) return false; + double curr_time = get_time_double(); + double time_since_start = curr_time - ctx->start_time; + double time_since_last_frame = curr_time - ctx->last_frame_time; + ctx->last_frame_time = curr_time; + int64_t video_pts = (int64_t)(time_since_start * ctx->video_encoder->time_base.den / ctx->video_encoder->time_base.num); - if (curr_pts >= next_pts) { - int err = av_frame_make_writable(ctx->video_frame); + size_t audio_size = ctx->pulseaudio + ? (size_t)(time_since_last_frame * 44100 * 2 * sizeof(int16_t)) + : 0; + int err; + while (audio_size > 0 && audio_size < SIZE_MAX / 2 /* just in case time goes backwards somehow */) { + uint8_t buf[4096]; + size_t n = audio_size > sizeof buf ? sizeof buf : audio_size; + if (pa_simple_read(ctx->pulseaudio, buf, n, &err) < 0) { + static atomic_flag warned = ATOMIC_FLAG_INIT; + if (!atomic_flag_test_and_set_explicit(&warned, memory_order_relaxed)) { + fprintf(stderr, "error: pa_simple_read: %s\n", pa_strerror(err)); + } + break; + } + audio_size -= n; + } + if (video_pts >= ctx->next_video_pts) { + err = av_frame_make_writable(ctx->video_frame); if (err < 0) { fprintf(stderr, "error: av_frame_make_writable: %s\n", av_err2str(err)); return false; } - ctx->video_frame->pts = curr_pts; + ctx->video_frame->pts = video_pts; camera_copy_to_av_frame(camera, ctx->video_frame); write_frame(ctx, ctx->video_encoder, ctx->video_stream, ctx->video_frame); - ctx->video_pts = curr_pts + 1; + ctx->next_video_pts = video_pts + 1; } return true; } @@ -154,6 +194,10 @@ bool video_is_recording(VideoContext *ctx) { void video_stop(VideoContext *ctx) { if (!ctx) return; if (ctx->recording) { + if (ctx->pulseaudio) { + pa_simple_free(ctx->pulseaudio); + ctx->pulseaudio = NULL; + } ctx->recording = false; // flush video encoder write_frame(ctx, ctx->video_encoder, ctx->video_stream, NULL); |