summaryrefslogtreecommitdiff
path: root/video.c
diff options
context:
space:
mode:
Diffstat (limited to 'video.c')
-rw-r--r--video.c68
1 files changed, 56 insertions, 12 deletions
diff --git a/video.c b/video.c
index 536f0dc..d2dd374 100644
--- a/video.c
+++ b/video.c
@@ -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);