mirror of
https://repo.dec05eba.com/gpu-screen-recorder
synced 2026-03-31 09:07:13 +09:00
Move encoding code from video encoder to encoder, since it also processes audio input
This commit is contained in:
44
include/encoder/encoder.h
Normal file
44
include/encoder/encoder.h
Normal file
@@ -0,0 +1,44 @@
|
|||||||
|
#ifndef GSR_ENCODER_H
|
||||||
|
#define GSR_ENCODER_H
|
||||||
|
|
||||||
|
#include "../replay_buffer.h"
|
||||||
|
#include <stdbool.h>
|
||||||
|
#include <stdint.h>
|
||||||
|
#include <stddef.h>
|
||||||
|
#include <pthread.h>
|
||||||
|
|
||||||
|
#define GSR_MAX_RECORDING_DESTINATIONS 128
|
||||||
|
|
||||||
|
typedef struct AVCodecContext AVCodecContext;
|
||||||
|
typedef struct AVFormatContext AVFormatContext;
|
||||||
|
typedef struct AVStream AVStream;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
size_t id;
|
||||||
|
AVCodecContext *codec_context;
|
||||||
|
AVFormatContext *format_context;
|
||||||
|
AVStream *stream;
|
||||||
|
int64_t start_pts;
|
||||||
|
bool has_received_keyframe;
|
||||||
|
} gsr_encoder_recording_destination;
|
||||||
|
|
||||||
|
typedef struct {
|
||||||
|
gsr_replay_buffer replay_buffer;
|
||||||
|
bool has_replay_buffer;
|
||||||
|
pthread_mutex_t file_write_mutex;
|
||||||
|
bool mutex_created;
|
||||||
|
|
||||||
|
gsr_encoder_recording_destination recording_destinations[GSR_MAX_RECORDING_DESTINATIONS];
|
||||||
|
size_t num_recording_destinations;
|
||||||
|
size_t recording_destination_id_counter;
|
||||||
|
} gsr_encoder;
|
||||||
|
|
||||||
|
bool gsr_encoder_init(gsr_encoder *self, size_t replay_buffer_num_packets);
|
||||||
|
void gsr_encoder_deinit(gsr_encoder *self);
|
||||||
|
|
||||||
|
void gsr_encoder_receive_packets(gsr_encoder *self, AVCodecContext *codec_context, int64_t pts, int stream_index);
|
||||||
|
/* Returns the id to the recording destination, or -1 on error */
|
||||||
|
size_t gsr_encoder_add_recording_destination(gsr_encoder *self, AVCodecContext *codec_context, AVFormatContext *format_context, AVStream *stream, int64_t start_pts);
|
||||||
|
bool gsr_encoder_remove_recording_destination(gsr_encoder *self, size_t id);
|
||||||
|
|
||||||
|
#endif /* GSR_ENCODER_H */
|
||||||
@@ -2,27 +2,13 @@
|
|||||||
#define GSR_ENCODER_VIDEO_H
|
#define GSR_ENCODER_VIDEO_H
|
||||||
|
|
||||||
#include "../../color_conversion.h"
|
#include "../../color_conversion.h"
|
||||||
#include "../../replay_buffer.h"
|
|
||||||
#include <stdbool.h>
|
#include <stdbool.h>
|
||||||
#include <stdint.h>
|
|
||||||
#include <pthread.h>
|
|
||||||
|
|
||||||
#define GSR_MAX_RECORDING_DESTINATIONS 128
|
#define GSR_MAX_RECORDING_DESTINATIONS 128
|
||||||
|
|
||||||
typedef struct gsr_video_encoder gsr_video_encoder;
|
typedef struct gsr_video_encoder gsr_video_encoder;
|
||||||
typedef struct AVCodecContext AVCodecContext;
|
typedef struct AVCodecContext AVCodecContext;
|
||||||
typedef struct AVFormatContext AVFormatContext;
|
|
||||||
typedef struct AVFrame AVFrame;
|
typedef struct AVFrame AVFrame;
|
||||||
typedef struct AVStream AVStream;
|
|
||||||
|
|
||||||
typedef struct {
|
|
||||||
size_t id;
|
|
||||||
AVCodecContext *codec_context;
|
|
||||||
AVFormatContext *format_context;
|
|
||||||
AVStream *stream;
|
|
||||||
int64_t start_pts;
|
|
||||||
bool has_received_keyframe;
|
|
||||||
} gsr_video_encoder_recording_destination;
|
|
||||||
|
|
||||||
struct gsr_video_encoder {
|
struct gsr_video_encoder {
|
||||||
bool (*start)(gsr_video_encoder *encoder, AVCodecContext *video_codec_context, AVFrame *frame);
|
bool (*start)(gsr_video_encoder *encoder, AVCodecContext *video_codec_context, AVFrame *frame);
|
||||||
@@ -33,24 +19,12 @@ struct gsr_video_encoder {
|
|||||||
|
|
||||||
void *priv;
|
void *priv;
|
||||||
bool started;
|
bool started;
|
||||||
gsr_replay_buffer replay_buffer;
|
|
||||||
bool has_replay_buffer;
|
|
||||||
pthread_mutex_t file_write_mutex;
|
|
||||||
|
|
||||||
gsr_video_encoder_recording_destination recording_destinations[GSR_MAX_RECORDING_DESTINATIONS];
|
|
||||||
size_t num_recording_destinations;
|
|
||||||
size_t recording_destination_id;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/* Set |replay_buffer_time_seconds| and |fps| to 0 to disable replay buffer */
|
/* Set |replay_buffer_time_seconds| and |fps| to 0 to disable replay buffer */
|
||||||
bool gsr_video_encoder_start(gsr_video_encoder *encoder, AVCodecContext *video_codec_context, AVFrame *frame, size_t replay_buffer_num_packets);
|
bool gsr_video_encoder_start(gsr_video_encoder *encoder, AVCodecContext *video_codec_context, AVFrame *frame);
|
||||||
void gsr_video_encoder_destroy(gsr_video_encoder *encoder, AVCodecContext *video_codec_context);
|
void gsr_video_encoder_destroy(gsr_video_encoder *encoder, AVCodecContext *video_codec_context);
|
||||||
void gsr_video_encoder_copy_textures_to_frame(gsr_video_encoder *encoder, AVFrame *frame, gsr_color_conversion *color_conversion);
|
void gsr_video_encoder_copy_textures_to_frame(gsr_video_encoder *encoder, AVFrame *frame, gsr_color_conversion *color_conversion);
|
||||||
void gsr_video_encoder_get_textures(gsr_video_encoder *encoder, unsigned int *textures, int *num_textures, gsr_destination_color *destination_color);
|
void gsr_video_encoder_get_textures(gsr_video_encoder *encoder, unsigned int *textures, int *num_textures, gsr_destination_color *destination_color);
|
||||||
|
|
||||||
void gsr_video_encoder_receive_packets(gsr_video_encoder *encoder, AVCodecContext *codec_context, int64_t pts, int stream_index);
|
|
||||||
/* Returns the id to the recording destination, or -1 on error */
|
|
||||||
size_t gsr_video_encoder_add_recording_destination(gsr_video_encoder *encoder, AVCodecContext *codec_context, AVFormatContext *format_context, AVStream *stream, int64_t start_pts);
|
|
||||||
bool gsr_video_encoder_remove_recording_destination(gsr_video_encoder *encoder, size_t id);
|
|
||||||
|
|
||||||
#endif /* GSR_ENCODER_VIDEO_H */
|
#endif /* GSR_ENCODER_VIDEO_H */
|
||||||
|
|||||||
@@ -14,6 +14,7 @@ src = [
|
|||||||
'src/capture/xcomposite.c',
|
'src/capture/xcomposite.c',
|
||||||
'src/capture/ximage.c',
|
'src/capture/ximage.c',
|
||||||
'src/capture/kms.c',
|
'src/capture/kms.c',
|
||||||
|
'src/encoder/encoder.c',
|
||||||
'src/encoder/video/video.c',
|
'src/encoder/video/video.c',
|
||||||
'src/encoder/video/nvenc.c',
|
'src/encoder/video/nvenc.c',
|
||||||
'src/encoder/video/vaapi.c',
|
'src/encoder/video/vaapi.c',
|
||||||
|
|||||||
152
src/encoder/encoder.c
Normal file
152
src/encoder/encoder.c
Normal file
@@ -0,0 +1,152 @@
|
|||||||
|
#include "../../include/encoder/encoder.h"
|
||||||
|
#include "../../include/utils.h"
|
||||||
|
|
||||||
|
#include <string.h>
|
||||||
|
#include <stdio.h>
|
||||||
|
|
||||||
|
#include <libavcodec/avcodec.h>
|
||||||
|
#include <libavformat/avformat.h>
|
||||||
|
|
||||||
|
bool gsr_encoder_init(gsr_encoder *self, size_t replay_buffer_num_packets) {
|
||||||
|
memset(self, 0, sizeof(*self));
|
||||||
|
self->num_recording_destinations = 0;
|
||||||
|
self->recording_destination_id_counter = 0;
|
||||||
|
|
||||||
|
if(pthread_mutex_init(&self->file_write_mutex, NULL) != 0) {
|
||||||
|
fprintf(stderr, "gsr error: gsr_encoder_init: failed to create mutex\n");
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self->mutex_created = true;
|
||||||
|
|
||||||
|
if(replay_buffer_num_packets > 0) {
|
||||||
|
if(!gsr_replay_buffer_init(&self->replay_buffer, replay_buffer_num_packets)) {
|
||||||
|
fprintf(stderr, "gsr error: gsr_encoder_init: failed to create replay buffer\n");
|
||||||
|
gsr_encoder_deinit(self);
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
self->has_replay_buffer = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gsr_encoder_deinit(gsr_encoder *self) {
|
||||||
|
if(self->mutex_created) {
|
||||||
|
self->mutex_created = false;
|
||||||
|
pthread_mutex_destroy(&self->file_write_mutex);
|
||||||
|
}
|
||||||
|
|
||||||
|
gsr_replay_buffer_deinit(&self->replay_buffer);
|
||||||
|
self->has_replay_buffer = false;
|
||||||
|
self->num_recording_destinations = 0;
|
||||||
|
self->recording_destination_id_counter = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void gsr_encoder_receive_packets(gsr_encoder *self, AVCodecContext *codec_context, int64_t pts, int stream_index) {
|
||||||
|
for(;;) {
|
||||||
|
AVPacket *av_packet = av_packet_alloc();
|
||||||
|
if(!av_packet)
|
||||||
|
break;
|
||||||
|
|
||||||
|
av_packet->data = NULL;
|
||||||
|
av_packet->size = 0;
|
||||||
|
int res = avcodec_receive_packet(codec_context, av_packet);
|
||||||
|
if(res == 0) { // we have a packet, send the packet to the muxer
|
||||||
|
av_packet->stream_index = stream_index;
|
||||||
|
av_packet->pts = pts;
|
||||||
|
av_packet->dts = pts;
|
||||||
|
|
||||||
|
if(self->has_replay_buffer) {
|
||||||
|
const double time_now = clock_get_monotonic_seconds();
|
||||||
|
if(!gsr_replay_buffer_append(&self->replay_buffer, av_packet, time_now))
|
||||||
|
fprintf(stderr, "gsr error: gsr_encoder_receive_packets: failed to add replay buffer data\n");
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&self->file_write_mutex);
|
||||||
|
const bool is_keyframe = av_packet->flags & AV_PKT_FLAG_KEY;
|
||||||
|
for(size_t i = 0; i < self->num_recording_destinations; ++i) {
|
||||||
|
gsr_encoder_recording_destination *recording_destination = &self->recording_destinations[i];
|
||||||
|
if(recording_destination->codec_context != codec_context)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
if(is_keyframe)
|
||||||
|
recording_destination->has_received_keyframe = true;
|
||||||
|
else if(!recording_destination->has_received_keyframe)
|
||||||
|
continue;
|
||||||
|
|
||||||
|
av_packet->pts = pts - recording_destination->start_pts;
|
||||||
|
av_packet->dts = pts - recording_destination->start_pts;
|
||||||
|
|
||||||
|
av_packet_rescale_ts(av_packet, codec_context->time_base, recording_destination->stream->time_base);
|
||||||
|
// TODO: Is av_interleaved_write_frame needed?. Answer: might be needed for mkv but dont use it! it causes frames to be inconsistent, skipping frames and duplicating frames.
|
||||||
|
// TODO: av_interleaved_write_frame might be needed for cfr, or always for flv
|
||||||
|
const int ret = av_write_frame(recording_destination->format_context, av_packet);
|
||||||
|
if(ret < 0) {
|
||||||
|
char error_buffer[AV_ERROR_MAX_STRING_SIZE];
|
||||||
|
if(av_strerror(ret, error_buffer, sizeof(error_buffer)) < 0)
|
||||||
|
snprintf(error_buffer, sizeof(error_buffer), "Unknown error");
|
||||||
|
fprintf(stderr, "gsr error: gsr_encoder_receive_packets: failed to write frame index %d to muxer, reason: %s (%d)\n", av_packet->stream_index, error_buffer, ret);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&self->file_write_mutex);
|
||||||
|
|
||||||
|
av_packet_free(&av_packet);
|
||||||
|
} else if (res == AVERROR(EAGAIN)) { // we have no packet
|
||||||
|
// fprintf(stderr, "No packet!\n");
|
||||||
|
av_packet_free(&av_packet);
|
||||||
|
break;
|
||||||
|
} else if (res == AVERROR_EOF) { // this is the end of the stream
|
||||||
|
av_packet_free(&av_packet);
|
||||||
|
fprintf(stderr, "End of stream!\n");
|
||||||
|
break;
|
||||||
|
} else {
|
||||||
|
av_packet_free(&av_packet);
|
||||||
|
fprintf(stderr, "Unexpected error: %d\n", res);
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t gsr_encoder_add_recording_destination(gsr_encoder *self, AVCodecContext *codec_context, AVFormatContext *format_context, AVStream *stream, int64_t start_pts) {
|
||||||
|
if(self->num_recording_destinations >= GSR_MAX_RECORDING_DESTINATIONS) {
|
||||||
|
fprintf(stderr, "gsr error: gsr_encoder_add_recording_destination: failed to add destination, reached the max amount of recording destinations (%d)\n", GSR_MAX_RECORDING_DESTINATIONS);
|
||||||
|
return (size_t)-1;
|
||||||
|
}
|
||||||
|
|
||||||
|
for(size_t i = 0; i < self->num_recording_destinations; ++i) {
|
||||||
|
if(self->recording_destinations[i].stream == stream) {
|
||||||
|
fprintf(stderr, "gsr error: gsr_encoder_add_recording_destination: failed to add destination, the stream %p already exists as an output\n", (void*)stream);
|
||||||
|
return (size_t)-1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pthread_mutex_lock(&self->file_write_mutex);
|
||||||
|
gsr_encoder_recording_destination *recording_destination = &self->recording_destinations[self->num_recording_destinations];
|
||||||
|
recording_destination->id = self->recording_destination_id_counter;
|
||||||
|
recording_destination->codec_context = codec_context;
|
||||||
|
recording_destination->format_context = format_context;
|
||||||
|
recording_destination->stream = stream;
|
||||||
|
recording_destination->start_pts = start_pts;
|
||||||
|
recording_destination->has_received_keyframe = false;
|
||||||
|
|
||||||
|
++self->recording_destination_id_counter;
|
||||||
|
++self->num_recording_destinations;
|
||||||
|
pthread_mutex_unlock(&self->file_write_mutex);
|
||||||
|
|
||||||
|
return recording_destination->id;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool gsr_encoder_remove_recording_destination(gsr_encoder *self, size_t id) {
|
||||||
|
bool found = false;
|
||||||
|
pthread_mutex_lock(&self->file_write_mutex);
|
||||||
|
for(size_t i = 0; i < self->num_recording_destinations; ++i) {
|
||||||
|
if(self->recording_destinations[i].id == id) {
|
||||||
|
self->recording_destinations[i] = self->recording_destinations[self->num_recording_destinations - 1];
|
||||||
|
--self->num_recording_destinations;
|
||||||
|
found = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pthread_mutex_unlock(&self->file_write_mutex);
|
||||||
|
return found;
|
||||||
|
}
|
||||||
@@ -1,54 +1,18 @@
|
|||||||
#include "../../../include/encoder/video/video.h"
|
#include "../../../include/encoder/video/video.h"
|
||||||
#include "../../../include/utils.h"
|
|
||||||
|
|
||||||
#include <string.h>
|
|
||||||
#include <stdio.h>
|
|
||||||
#include <assert.h>
|
#include <assert.h>
|
||||||
|
|
||||||
#include <libavcodec/avcodec.h>
|
bool gsr_video_encoder_start(gsr_video_encoder *encoder, AVCodecContext *video_codec_context, AVFrame *frame) {
|
||||||
#include <libavformat/avformat.h>
|
|
||||||
|
|
||||||
bool gsr_video_encoder_start(gsr_video_encoder *encoder, AVCodecContext *video_codec_context, AVFrame *frame, size_t replay_buffer_num_packets) {
|
|
||||||
assert(!encoder->started);
|
assert(!encoder->started);
|
||||||
encoder->num_recording_destinations = 0;
|
|
||||||
encoder->recording_destination_id = 0;
|
|
||||||
|
|
||||||
if(pthread_mutex_init(&encoder->file_write_mutex, NULL) != 0) {
|
|
||||||
fprintf(stderr, "gsr error: gsr_video_encoder_start: failed to create mutex\n");
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
memset(&encoder->replay_buffer, 0, sizeof(encoder->replay_buffer));
|
|
||||||
if(replay_buffer_num_packets > 0) {
|
|
||||||
if(!gsr_replay_buffer_init(&encoder->replay_buffer, replay_buffer_num_packets)) {
|
|
||||||
fprintf(stderr, "gsr error: gsr_video_encoder_start: failed to create replay buffer\n");
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
encoder->has_replay_buffer = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool res = encoder->start(encoder, video_codec_context, frame);
|
bool res = encoder->start(encoder, video_codec_context, frame);
|
||||||
if(res) {
|
if(res)
|
||||||
encoder->started = true;
|
encoder->started = true;
|
||||||
return true;
|
return res;
|
||||||
} else {
|
|
||||||
goto error;
|
|
||||||
}
|
|
||||||
|
|
||||||
error:
|
|
||||||
pthread_mutex_destroy(&encoder->file_write_mutex);
|
|
||||||
gsr_replay_buffer_deinit(&encoder->replay_buffer);
|
|
||||||
return false;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void gsr_video_encoder_destroy(gsr_video_encoder *encoder, AVCodecContext *video_codec_context) {
|
void gsr_video_encoder_destroy(gsr_video_encoder *encoder, AVCodecContext *video_codec_context) {
|
||||||
assert(encoder->started);
|
assert(encoder->started);
|
||||||
encoder->started = false;
|
encoder->started = false;
|
||||||
pthread_mutex_destroy(&encoder->file_write_mutex);
|
|
||||||
gsr_replay_buffer_deinit(&encoder->replay_buffer);
|
|
||||||
encoder->has_replay_buffer = false;
|
|
||||||
encoder->num_recording_destinations = 0;
|
|
||||||
encoder->recording_destination_id = 0;
|
|
||||||
encoder->destroy(encoder, video_codec_context);
|
encoder->destroy(encoder, video_codec_context);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -62,112 +26,3 @@ void gsr_video_encoder_get_textures(gsr_video_encoder *encoder, unsigned int *te
|
|||||||
assert(encoder->started);
|
assert(encoder->started);
|
||||||
encoder->get_textures(encoder, textures, num_textures, destination_color);
|
encoder->get_textures(encoder, textures, num_textures, destination_color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void gsr_video_encoder_receive_packets(gsr_video_encoder *encoder, AVCodecContext *codec_context, int64_t pts, int stream_index) {
|
|
||||||
for (;;) {
|
|
||||||
AVPacket *av_packet = av_packet_alloc();
|
|
||||||
if(!av_packet)
|
|
||||||
break;
|
|
||||||
|
|
||||||
av_packet->data = NULL;
|
|
||||||
av_packet->size = 0;
|
|
||||||
int res = avcodec_receive_packet(codec_context, av_packet);
|
|
||||||
if(res == 0) { // we have a packet, send the packet to the muxer
|
|
||||||
av_packet->stream_index = stream_index;
|
|
||||||
av_packet->pts = pts;
|
|
||||||
av_packet->dts = pts;
|
|
||||||
|
|
||||||
if(encoder->has_replay_buffer) {
|
|
||||||
const double time_now = clock_get_monotonic_seconds();
|
|
||||||
if(!gsr_replay_buffer_append(&encoder->replay_buffer, av_packet, time_now))
|
|
||||||
fprintf(stderr, "gsr error: gsr_video_encoder_receive_packets: failed to add replay buffer data\n");
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_mutex_lock(&encoder->file_write_mutex);
|
|
||||||
const bool is_keyframe = av_packet->flags & AV_PKT_FLAG_KEY;
|
|
||||||
for(size_t i = 0; i < encoder->num_recording_destinations; ++i) {
|
|
||||||
gsr_video_encoder_recording_destination *recording_destination = &encoder->recording_destinations[i];
|
|
||||||
if(recording_destination->codec_context != codec_context)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if(is_keyframe)
|
|
||||||
recording_destination->has_received_keyframe = true;
|
|
||||||
else if(!recording_destination->has_received_keyframe)
|
|
||||||
continue;
|
|
||||||
|
|
||||||
av_packet->pts = pts - recording_destination->start_pts;
|
|
||||||
av_packet->dts = pts - recording_destination->start_pts;
|
|
||||||
|
|
||||||
av_packet_rescale_ts(av_packet, codec_context->time_base, recording_destination->stream->time_base);
|
|
||||||
// TODO: Is av_interleaved_write_frame needed?. Answer: might be needed for mkv but dont use it! it causes frames to be inconsistent, skipping frames and duplicating frames.
|
|
||||||
// TODO: av_interleaved_write_frame might be needed for cfr, or always for flv
|
|
||||||
const int ret = av_write_frame(recording_destination->format_context, av_packet);
|
|
||||||
if(ret < 0) {
|
|
||||||
char error_buffer[AV_ERROR_MAX_STRING_SIZE];
|
|
||||||
if(av_strerror(ret, error_buffer, sizeof(error_buffer)) < 0)
|
|
||||||
snprintf(error_buffer, sizeof(error_buffer), "Unknown error");
|
|
||||||
fprintf(stderr, "Error: Failed to write frame index %d to muxer, reason: %s (%d)\n", av_packet->stream_index, error_buffer, ret);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&encoder->file_write_mutex);
|
|
||||||
|
|
||||||
av_packet_free(&av_packet);
|
|
||||||
} else if (res == AVERROR(EAGAIN)) { // we have no packet
|
|
||||||
// fprintf(stderr, "No packet!\n");
|
|
||||||
av_packet_free(&av_packet);
|
|
||||||
break;
|
|
||||||
} else if (res == AVERROR_EOF) { // this is the end of the stream
|
|
||||||
av_packet_free(&av_packet);
|
|
||||||
fprintf(stderr, "End of stream!\n");
|
|
||||||
break;
|
|
||||||
} else {
|
|
||||||
av_packet_free(&av_packet);
|
|
||||||
fprintf(stderr, "Unexpected error: %d\n", res);
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
size_t gsr_video_encoder_add_recording_destination(gsr_video_encoder *encoder, AVCodecContext *codec_context, AVFormatContext *format_context, AVStream *stream, int64_t start_pts) {
|
|
||||||
if(encoder->num_recording_destinations >= GSR_MAX_RECORDING_DESTINATIONS) {
|
|
||||||
fprintf(stderr, "gsr error: gsr_video_encoder_add_recording_destination: failed to add destination, reached the max amount of recording destinations (%d)\n", GSR_MAX_RECORDING_DESTINATIONS);
|
|
||||||
return (size_t)-1;
|
|
||||||
}
|
|
||||||
|
|
||||||
for(size_t i = 0; i < encoder->num_recording_destinations; ++i) {
|
|
||||||
if(encoder->recording_destinations[i].stream == stream) {
|
|
||||||
fprintf(stderr, "gsr error: gsr_video_encoder_add_recording_destination: failed to add destination, the stream %p already exists as an output\n", (void*)stream);
|
|
||||||
return (size_t)-1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
pthread_mutex_lock(&encoder->file_write_mutex);
|
|
||||||
gsr_video_encoder_recording_destination *recording_destination = &encoder->recording_destinations[encoder->num_recording_destinations];
|
|
||||||
recording_destination->id = encoder->recording_destination_id;
|
|
||||||
recording_destination->codec_context = codec_context;
|
|
||||||
recording_destination->format_context = format_context;
|
|
||||||
recording_destination->stream = stream;
|
|
||||||
recording_destination->start_pts = start_pts;
|
|
||||||
recording_destination->has_received_keyframe = false;
|
|
||||||
|
|
||||||
++encoder->recording_destination_id;
|
|
||||||
++encoder->num_recording_destinations;
|
|
||||||
pthread_mutex_unlock(&encoder->file_write_mutex);
|
|
||||||
|
|
||||||
return recording_destination->id;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool gsr_video_encoder_remove_recording_destination(gsr_video_encoder *encoder, size_t id) {
|
|
||||||
bool found = false;
|
|
||||||
pthread_mutex_lock(&encoder->file_write_mutex);
|
|
||||||
for(size_t i = 0; i < encoder->num_recording_destinations; ++i) {
|
|
||||||
if(encoder->recording_destinations[i].id == id) {
|
|
||||||
encoder->recording_destinations[i] = encoder->recording_destinations[encoder->num_recording_destinations - 1];
|
|
||||||
--encoder->num_recording_destinations;
|
|
||||||
found = true;
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pthread_mutex_unlock(&encoder->file_write_mutex);
|
|
||||||
return found;
|
|
||||||
}
|
|
||||||
|
|||||||
36
src/main.cpp
36
src/main.cpp
@@ -10,6 +10,7 @@ extern "C" {
|
|||||||
#ifdef GSR_APP_AUDIO
|
#ifdef GSR_APP_AUDIO
|
||||||
#include "../include/pipewire_audio.h"
|
#include "../include/pipewire_audio.h"
|
||||||
#endif
|
#endif
|
||||||
|
#include "../include/encoder/encoder.h"
|
||||||
#include "../include/encoder/video/nvenc.h"
|
#include "../include/encoder/video/nvenc.h"
|
||||||
#include "../include/encoder/video/vaapi.h"
|
#include "../include/encoder/video/vaapi.h"
|
||||||
#include "../include/encoder/video/vulkan.h"
|
#include "../include/encoder/video/vulkan.h"
|
||||||
@@ -3143,14 +3144,20 @@ int main(int argc, char **argv) {
|
|||||||
video_frame->width = capture_metadata.width;
|
video_frame->width = capture_metadata.width;
|
||||||
video_frame->height = capture_metadata.height;
|
video_frame->height = capture_metadata.height;
|
||||||
|
|
||||||
|
const size_t estimated_replay_buffer_packets = calculate_estimated_replay_buffer_packets(arg_parser.replay_buffer_size_secs, arg_parser.fps, arg_parser.audio_codec, requested_audio_inputs);
|
||||||
|
gsr_encoder encoder;
|
||||||
|
if(!gsr_encoder_init(&encoder, estimated_replay_buffer_packets)) {
|
||||||
|
fprintf(stderr, "Error: failed to create encoder\n");
|
||||||
|
_exit(1);
|
||||||
|
}
|
||||||
|
|
||||||
gsr_video_encoder *video_encoder = create_video_encoder(&egl, arg_parser);
|
gsr_video_encoder *video_encoder = create_video_encoder(&egl, arg_parser);
|
||||||
if(!video_encoder) {
|
if(!video_encoder) {
|
||||||
fprintf(stderr, "Error: failed to create video encoder\n");
|
fprintf(stderr, "Error: failed to create video encoder\n");
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
const size_t estimated_replay_buffer_packets = calculate_estimated_replay_buffer_packets(arg_parser.replay_buffer_size_secs, arg_parser.fps, arg_parser.audio_codec, requested_audio_inputs);
|
if(!gsr_video_encoder_start(video_encoder, video_codec_context, video_frame)) {
|
||||||
if(!gsr_video_encoder_start(video_encoder, video_codec_context, video_frame, estimated_replay_buffer_packets)) {
|
|
||||||
fprintf(stderr, "Error: failed to start video encoder\n");
|
fprintf(stderr, "Error: failed to start video encoder\n");
|
||||||
_exit(1);
|
_exit(1);
|
||||||
}
|
}
|
||||||
@@ -3181,7 +3188,7 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
if(video_stream) {
|
if(video_stream) {
|
||||||
avcodec_parameters_from_context(video_stream->codecpar, video_codec_context);
|
avcodec_parameters_from_context(video_stream->codecpar, video_codec_context);
|
||||||
gsr_video_encoder_add_recording_destination(video_encoder, video_codec_context, av_format_context, video_stream, 0);
|
gsr_encoder_add_recording_destination(&encoder, video_codec_context, av_format_context, video_stream, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
int audio_max_frame_size = 1024;
|
int audio_max_frame_size = 1024;
|
||||||
@@ -3193,7 +3200,7 @@ int main(int argc, char **argv) {
|
|||||||
AVStream *audio_stream = nullptr;
|
AVStream *audio_stream = nullptr;
|
||||||
if(!is_replaying) {
|
if(!is_replaying) {
|
||||||
audio_stream = create_stream(av_format_context, audio_codec_context);
|
audio_stream = create_stream(av_format_context, audio_codec_context);
|
||||||
if(gsr_video_encoder_add_recording_destination(video_encoder, audio_codec_context, av_format_context, audio_stream, 0) == (size_t)-1)
|
if(gsr_encoder_add_recording_destination(&encoder, audio_codec_context, av_format_context, audio_stream, 0) == (size_t)-1)
|
||||||
fprintf(stderr, "gsr error: added too many audio sources\n");
|
fprintf(stderr, "gsr error: added too many audio sources\n");
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -3396,7 +3403,7 @@ int main(int argc, char **argv) {
|
|||||||
ret = avcodec_send_frame(audio_track.codec_context, audio_device.frame);
|
ret = avcodec_send_frame(audio_track.codec_context, audio_device.frame);
|
||||||
if(ret >= 0) {
|
if(ret >= 0) {
|
||||||
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
||||||
gsr_video_encoder_receive_packets(video_encoder, audio_track.codec_context, audio_device.frame->pts, audio_track.stream_index);
|
gsr_encoder_receive_packets(&encoder, audio_track.codec_context, audio_device.frame->pts, audio_track.stream_index);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Failed to encode audio!\n");
|
fprintf(stderr, "Failed to encode audio!\n");
|
||||||
}
|
}
|
||||||
@@ -3430,7 +3437,7 @@ int main(int argc, char **argv) {
|
|||||||
ret = avcodec_send_frame(audio_track.codec_context, audio_device.frame);
|
ret = avcodec_send_frame(audio_track.codec_context, audio_device.frame);
|
||||||
if(ret >= 0) {
|
if(ret >= 0) {
|
||||||
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
||||||
gsr_video_encoder_receive_packets(video_encoder, audio_track.codec_context, audio_device.frame->pts, audio_track.stream_index);
|
gsr_encoder_receive_packets(&encoder, audio_track.codec_context, audio_device.frame->pts, audio_track.stream_index);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Failed to encode audio!\n");
|
fprintf(stderr, "Failed to encode audio!\n");
|
||||||
}
|
}
|
||||||
@@ -3465,7 +3472,7 @@ int main(int argc, char **argv) {
|
|||||||
err = avcodec_send_frame(audio_track.codec_context, aframe);
|
err = avcodec_send_frame(audio_track.codec_context, aframe);
|
||||||
if(err >= 0){
|
if(err >= 0){
|
||||||
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
||||||
gsr_video_encoder_receive_packets(video_encoder, audio_track.codec_context, aframe->pts, audio_track.stream_index);
|
gsr_encoder_receive_packets(&encoder, audio_track.codec_context, aframe->pts, audio_track.stream_index);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Failed to encode audio!\n");
|
fprintf(stderr, "Failed to encode audio!\n");
|
||||||
}
|
}
|
||||||
@@ -3616,7 +3623,7 @@ int main(int argc, char **argv) {
|
|||||||
int ret = avcodec_send_frame(video_codec_context, video_frame);
|
int ret = avcodec_send_frame(video_codec_context, video_frame);
|
||||||
if(ret == 0) {
|
if(ret == 0) {
|
||||||
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
// TODO: Move to separate thread because this could write to network (for example when livestreaming)
|
||||||
gsr_video_encoder_receive_packets(video_encoder, video_codec_context, video_frame->pts, VIDEO_STREAM_INDEX);
|
gsr_encoder_receive_packets(&encoder, video_codec_context, video_frame->pts, VIDEO_STREAM_INDEX);
|
||||||
} else {
|
} else {
|
||||||
fprintf(stderr, "Error: avcodec_send_frame failed, error: %s\n", av_error_to_string(ret));
|
fprintf(stderr, "Error: avcodec_send_frame failed, error: %s\n", av_error_to_string(ret));
|
||||||
}
|
}
|
||||||
@@ -3659,12 +3666,12 @@ int main(int argc, char **argv) {
|
|||||||
replay_recording_filepath = create_new_recording_filepath_from_timestamp(arg_parser.replay_recording_directory, "Recording", file_extension, arg_parser.date_folders);
|
replay_recording_filepath = create_new_recording_filepath_from_timestamp(arg_parser.replay_recording_directory, "Recording", file_extension, arg_parser.date_folders);
|
||||||
replay_recording_start_result = start_recording_create_streams(replay_recording_filepath.c_str(), arg_parser.container_format, video_codec_context, audio_tracks, hdr, capture);
|
replay_recording_start_result = start_recording_create_streams(replay_recording_filepath.c_str(), arg_parser.container_format, video_codec_context, audio_tracks, hdr, capture);
|
||||||
if(replay_recording_start_result.av_format_context) {
|
if(replay_recording_start_result.av_format_context) {
|
||||||
const size_t video_recording_destination_id = gsr_video_encoder_add_recording_destination(video_encoder, video_codec_context, replay_recording_start_result.av_format_context, replay_recording_start_result.video_stream, video_frame->pts);
|
const size_t video_recording_destination_id = gsr_encoder_add_recording_destination(&encoder, video_codec_context, replay_recording_start_result.av_format_context, replay_recording_start_result.video_stream, video_frame->pts);
|
||||||
if(video_recording_destination_id != (size_t)-1)
|
if(video_recording_destination_id != (size_t)-1)
|
||||||
replay_recording_items.push_back(video_recording_destination_id);
|
replay_recording_items.push_back(video_recording_destination_id);
|
||||||
|
|
||||||
for(const auto &audio_input : replay_recording_start_result.audio_inputs) {
|
for(const auto &audio_input : replay_recording_start_result.audio_inputs) {
|
||||||
const size_t audio_recording_destination_id = gsr_video_encoder_add_recording_destination(video_encoder, audio_input.audio_track->codec_context, replay_recording_start_result.av_format_context, audio_input.stream, audio_input.audio_track->pts);
|
const size_t audio_recording_destination_id = gsr_encoder_add_recording_destination(&encoder, audio_input.audio_track->codec_context, replay_recording_start_result.av_format_context, audio_input.stream, audio_input.audio_track->pts);
|
||||||
if(audio_recording_destination_id != (size_t)-1)
|
if(audio_recording_destination_id != (size_t)-1)
|
||||||
replay_recording_items.push_back(audio_recording_destination_id);
|
replay_recording_items.push_back(audio_recording_destination_id);
|
||||||
}
|
}
|
||||||
@@ -3678,7 +3685,7 @@ int main(int argc, char **argv) {
|
|||||||
}
|
}
|
||||||
} else if(replay_recording_start_result.av_format_context) {
|
} else if(replay_recording_start_result.av_format_context) {
|
||||||
for(size_t id : replay_recording_items) {
|
for(size_t id : replay_recording_items) {
|
||||||
gsr_video_encoder_remove_recording_destination(video_encoder, id);
|
gsr_encoder_remove_recording_destination(&encoder, id);
|
||||||
}
|
}
|
||||||
replay_recording_items.clear();
|
replay_recording_items.clear();
|
||||||
|
|
||||||
@@ -3718,10 +3725,10 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
save_replay_seconds = 0;
|
save_replay_seconds = 0;
|
||||||
save_replay_output_filepath.clear();
|
save_replay_output_filepath.clear();
|
||||||
save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, &video_encoder->replay_buffer, arg_parser.filename, arg_parser.container_format, file_extension, arg_parser.date_folders, hdr, capture, current_save_replay_seconds);
|
save_replay_async(video_codec_context, VIDEO_STREAM_INDEX, audio_tracks, &encoder.replay_buffer, arg_parser.filename, arg_parser.container_format, file_extension, arg_parser.date_folders, hdr, capture, current_save_replay_seconds);
|
||||||
|
|
||||||
if(arg_parser.restart_replay_on_save && current_save_replay_seconds == save_replay_seconds_full) {
|
if(arg_parser.restart_replay_on_save && current_save_replay_seconds == save_replay_seconds_full) {
|
||||||
gsr_replay_buffer_clear(&video_encoder->replay_buffer);
|
gsr_replay_buffer_clear(&encoder.replay_buffer);
|
||||||
replay_start_time = clock_get_monotonic_seconds() - paused_time_offset;
|
replay_start_time = clock_get_monotonic_seconds() - paused_time_offset;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -3768,7 +3775,7 @@ int main(int argc, char **argv) {
|
|||||||
|
|
||||||
if(replay_recording_start_result.av_format_context) {
|
if(replay_recording_start_result.av_format_context) {
|
||||||
for(size_t id : replay_recording_items) {
|
for(size_t id : replay_recording_items) {
|
||||||
gsr_video_encoder_remove_recording_destination(video_encoder, id);
|
gsr_encoder_remove_recording_destination(&encoder, id);
|
||||||
}
|
}
|
||||||
replay_recording_items.clear();
|
replay_recording_items.clear();
|
||||||
|
|
||||||
@@ -3807,6 +3814,7 @@ int main(int argc, char **argv) {
|
|||||||
gsr_damage_deinit(&damage);
|
gsr_damage_deinit(&damage);
|
||||||
gsr_color_conversion_deinit(&color_conversion);
|
gsr_color_conversion_deinit(&color_conversion);
|
||||||
gsr_video_encoder_destroy(video_encoder, video_codec_context);
|
gsr_video_encoder_destroy(video_encoder, video_codec_context);
|
||||||
|
gsr_encoder_deinit(&encoder);
|
||||||
gsr_capture_destroy(capture);
|
gsr_capture_destroy(capture);
|
||||||
#ifdef GSR_APP_AUDIO
|
#ifdef GSR_APP_AUDIO
|
||||||
gsr_pipewire_audio_deinit(&pipewire_audio);
|
gsr_pipewire_audio_deinit(&pipewire_audio);
|
||||||
|
|||||||
Reference in New Issue
Block a user