diff --git a/libobs/obs-internal.h b/libobs/obs-internal.h index aaf3df066..a3b4ddf70 100644 --- a/libobs/obs-internal.h +++ b/libobs/obs-internal.h @@ -262,6 +262,11 @@ struct obs_output { void *data; struct obs_output_info info; obs_data_t settings; + + signal_handler_t signals; + proc_handler_t procs; + + bool valid; }; diff --git a/libobs/obs-output.c b/libobs/obs-output.c index 22c57d06f..121500075 100644 --- a/libobs/obs-output.c +++ b/libobs/obs-output.c @@ -39,38 +39,57 @@ obs_output_t obs_output_create(const char *id, const char *name, return NULL; } - output = bmalloc(sizeof(struct obs_output)); + output = bzalloc(sizeof(struct obs_output)); + + output->signals = signal_handler_create(); + if (!output->signals) + goto fail; + + output->procs = proc_handler_create(); + if (!output->procs) + goto fail; + output->info = *info; output->settings = obs_data_newref(settings); output->data = info->create(output->settings, output); - - if (!output->data) { - obs_data_release(output->settings); - bfree(output); - return NULL; - } + if (!output->data) + goto fail; output->name = bstrdup(name); pthread_mutex_lock(&obs->data.outputs_mutex); da_push_back(obs->data.outputs, &output); pthread_mutex_unlock(&obs->data.outputs_mutex); + + output->valid = true; + return output; + +fail: + obs_output_destroy(output); + return NULL; } void obs_output_destroy(obs_output_t output) { if (output) { - if (output->info.active) { - if (output->info.active(output->data)) - output->info.stop(output->data); + if (output->valid) { + if (output->info.active) { + if (output->info.active(output->data)) + output->info.stop(output->data); + } + + pthread_mutex_lock(&obs->data.outputs_mutex); + da_erase_item(obs->data.outputs, &output); + pthread_mutex_unlock(&obs->data.outputs_mutex); } - pthread_mutex_lock(&obs->data.outputs_mutex); - da_erase_item(obs->data.outputs, &output); - pthread_mutex_unlock(&obs->data.outputs_mutex); + if (output->data) + output->info.destroy(output->data); + + signal_handler_destroy(output->signals); + proc_handler_destroy(output->procs); - output->info.destroy(output->data); obs_data_release(output->settings); bfree(output->name); bfree(output); @@ -143,3 +162,13 @@ void obs_output_pause(obs_output_t output) if (output && output->info.pause) output->info.pause(output->data); } + +signal_handler_t obs_output_signalhandler(obs_output_t output) +{ + return output->signals; +} + +proc_handler_t obs_output_prochandler(obs_output_t output) +{ + return output->procs; +} diff --git a/libobs/obs.c b/libobs/obs.c index 7b1333663..811686ba7 100644 --- a/libobs/obs.c +++ b/libobs/obs.c @@ -487,6 +487,11 @@ void obs_shutdown(void) free_module(obs->modules.array+i); da_free(obs->modules); + da_free(obs->data.sources); + da_free(obs->data.outputs); + da_free(obs->data.encoders); + da_free(obs->data.displays); + bfree(obs); obs = NULL; } diff --git a/libobs/obs.h b/libobs/obs.h index d831252c1..5636c752b 100644 --- a/libobs/obs.h +++ b/libobs/obs.h @@ -677,6 +677,12 @@ EXPORT void obs_output_pause(obs_output_t output); /* Gets the current output settings string */ EXPORT obs_data_t obs_output_get_settings(obs_output_t output); +/** Returns the signal handler for an output */ +EXPORT signal_handler_t obs_output_signalhandler(obs_output_t output); + +/** Returns the procedure handler for an output */ +EXPORT proc_handler_t obs_output_prochandler(obs_output_t output); + /* ------------------------------------------------------------------------- */ /* Encoders */ diff --git a/libobs/util/darray.h b/libobs/util/darray.h index d4355aaed..3fd461750 100644 --- a/libobs/util/darray.h +++ b/libobs/util/darray.h @@ -125,7 +125,7 @@ static inline void darray_resize(const size_t element_size, if (size == dst->num) { return; } else if (size == 0) { - darray_free(dst); + dst->num = 0; return; } @@ -305,7 +305,7 @@ static inline void darray_erase(const size_t element_size, struct darray *dst, return; if (!--dst->num) { - darray_free(dst); + dst->num = 0; return; } @@ -336,7 +336,7 @@ static inline void darray_erase_range(const size_t element_size, darray_erase(element_size, dst, start); return; } else if (count == dst->num) { - darray_free(dst); + dst->num = 0; return; } diff --git a/obs/forms/OBSBasic.ui b/obs/forms/OBSBasic.ui index ca4522d38..f60778f46 100644 --- a/obs/forms/OBSBasic.ui +++ b/obs/forms/OBSBasic.ui @@ -319,7 +319,7 @@ - false + true Start Streaming @@ -331,6 +331,9 @@ + + false + Start Recording diff --git a/obs/forms/OBSBasicSettings.ui b/obs/forms/OBSBasicSettings.ui index 09813ce2e..22ae47c27 100644 --- a/obs/forms/OBSBasicSettings.ui +++ b/obs/forms/OBSBasicSettings.ui @@ -77,7 +77,7 @@ - 3 + 1 @@ -118,12 +118,101 @@ - - - + + QFormLayout::AllNonFixedFieldsGrow + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + NOTE: This is a test, just some temporary user interface + + + true + + + + + + + + 170 + 0 + + + + Video Bitrate: + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter + + + + + + + URL: + + + + + + + + + + Stream Key: + + + + + + + QLineEdit::Password + + + + + + + 100 + + + 60000 + + + 2500 + + + + + + + 24 + + + 320 + + + 128 + + + + + + + Audio Bitrate: + + + + + + + Qt::AlignRight|Qt::AlignTrailing|Qt::AlignVCenter diff --git a/obs/obs-app.cpp b/obs/obs-app.cpp index a6c982eba..8e6e40f19 100644 --- a/obs/obs-app.cpp +++ b/obs/obs-app.cpp @@ -43,7 +43,7 @@ static void do_log(int log_level, const char *msg, va_list args) OutputDebugStringA(bla); OutputDebugStringA("\n"); - if (log_level <= LOG_WARNING && IsDebuggerPresent()) + if (log_level <= LOG_ERROR && IsDebuggerPresent()) __debugbreak(); #else vprintf(msg, args); diff --git a/obs/window-basic-main.cpp b/obs/window-basic-main.cpp index ee3e6015c..531784d28 100644 --- a/obs/window-basic-main.cpp +++ b/obs/window-basic-main.cpp @@ -42,6 +42,7 @@ OBSBasic::OBSBasic(QWidget *parent) : OBSMainWindow (parent), outputTest (NULL), sceneChanging (false), + resizeTimer (0), ui (new Ui::OBSBasic) { ui->setupUi(this); @@ -66,6 +67,12 @@ bool OBSBasic::InitBasicConfigDefaults() uint32_t cx = monitors[0].cx; uint32_t cy = monitors[0].cy; + /* TODO: temporary */ + config_set_default_string(basicConfig, "OutputTemp", "URL", ""); + config_set_default_string(basicConfig, "OutputTemp", "Key", ""); + config_set_default_uint (basicConfig, "OutputTemp", "VBitrate", 2500); + config_set_default_uint (basicConfig, "OutputTemp", "ABitrate", 128); + config_set_default_uint (basicConfig, "Video", "BaseCX", cx); config_set_default_uint (basicConfig, "Video", "BaseCY", cy); @@ -732,33 +739,62 @@ void OBSBasic::on_actionSourceDown_triggered() { } -void OBSBasic::on_recordButton_clicked() +void OBSBasic::OutputConnect(bool success) +{ + if (!success) { + obs_output_destroy(outputTest); + outputTest = NULL; + } else { + ui->streamButton->setText("Stop Streaming"); + } + + ui->streamButton->setEnabled(true); +} + +static void OBSOutputConnect(void *data, calldata_t params) +{ + bool success = calldata_bool(params, "success"); + + QMetaObject::invokeMethod(static_cast(data), + "OutputConnect", Q_ARG(bool, success)); +} + +/* TODO: lots of temporary code */ +void OBSBasic::on_streamButton_clicked() { if (outputTest) { obs_output_destroy(outputTest); outputTest = NULL; - ui->recordButton->setText("Start Recording"); + ui->streamButton->setText("Start Streaming"); } else { - QString path = QFileDialog::getSaveFileName(this, - "Please enter a file name", QString(), - "MP4 Files (*.mp4)"); + const char *url = config_get_string(basicConfig, "OutputTemp", + "URL"); + const char *key = config_get_string(basicConfig, "OutputTemp", + "Key"); + int vBitrate = config_get_uint(basicConfig, "OutputTemp", + "VBitrate"); + int aBitrate = config_get_uint(basicConfig, "OutputTemp", + "ABitrate"); - if (path.isNull() || path.isEmpty()) - return; + string fullURL = url; + fullURL = fullURL + "/" + key; obs_data_t data = obs_data_create(); - obs_data_setstring(data, "filename", QT_TO_UTF8(path)); + obs_data_setstring(data, "filename", fullURL.c_str()); + obs_data_setint(data, "audio_bitrate", aBitrate); + obs_data_setint(data, "video_bitrate", vBitrate); outputTest = obs_output_create("ffmpeg_output", "test", data); obs_data_release(data); - if (!obs_output_start(outputTest)) { - obs_output_destroy(outputTest); - outputTest = NULL; + if (!outputTest) return; - } - ui->recordButton->setText("Stop Recording"); + signal_handler_connect(obs_output_signalhandler(outputTest), + "connect", OBSOutputConnect, this); + + obs_output_start(outputTest); + ui->streamButton->setEnabled(false); } } diff --git a/obs/window-basic-main.hpp b/obs/window-basic-main.hpp index bf81ff139..f551d10a2 100644 --- a/obs/window-basic-main.hpp +++ b/obs/window-basic-main.hpp @@ -57,6 +57,9 @@ private: void UpdateSources(OBSScene scene); void InsertSceneItem(obs_sceneitem_t item); +public slots: + void OutputConnect(bool success); + private slots: void AddSceneItem(OBSSceneItem item); void RemoveSceneItem(OBSSceneItem item); @@ -116,7 +119,7 @@ private slots: void on_actionSourceProperties_triggered(); void on_actionSourceUp_triggered(); void on_actionSourceDown_triggered(); - void on_recordButton_clicked(); + void on_streamButton_clicked(); void on_settingsButton_clicked(); public: diff --git a/obs/window-basic-settings.cpp b/obs/window-basic-settings.cpp index 9ddd62afd..d52177004 100644 --- a/obs/window-basic-settings.cpp +++ b/obs/window-basic-settings.cpp @@ -86,10 +86,12 @@ void OBSBasicSettings::HookWidget(QWidget *widget, const char *signal, #define COMBO_CHANGED SIGNAL(currentIndexChanged(int)) #define COMBO_CHANGED SIGNAL(currentIndexChanged(int)) +#define EDIT_CHANGED SIGNAL(textChanged(const QString &)) #define CBEDIT_CHANGED SIGNAL(editTextChanged(const QString &)) #define SCROLL_CHANGED SIGNAL(valueChanged(int)) #define GENERAL_CHANGED SLOT(GeneralChanged()) +#define OUTPUTS_CHANGED SLOT(OutputsChanged()) #define AUDIO_RESTART SLOT(AudioChangedRestart()) #define AUDIO_CHANGED SLOT(AudioChanged()) #define VIDEO_RESTART SLOT(VideoChangedRestart()) @@ -117,6 +119,10 @@ OBSBasicSettings::OBSBasicSettings(QWidget *parent) throw "Could not open locale.ini"; HookWidget(ui->language, COMBO_CHANGED, GENERAL_CHANGED); + HookWidget(ui->streamVBitrate, SCROLL_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->streamABitrate, SCROLL_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->streamURL, EDIT_CHANGED, OUTPUTS_CHANGED); + HookWidget(ui->streamKey, EDIT_CHANGED, OUTPUTS_CHANGED); HookWidget(ui->channelSetup, COMBO_CHANGED, AUDIO_RESTART); HookWidget(ui->sampleRate, COMBO_CHANGED, AUDIO_RESTART); HookWidget(ui->desktopAudioDevice1, COMBO_CHANGED, AUDIO_CHANGED); @@ -310,6 +316,27 @@ void OBSBasicSettings::LoadVideoSettings() loading = false; } +void OBSBasicSettings::LoadOutputSettings() +{ + loading = true; + + const char *url = config_get_string(main->Config(), "OutputTemp", + "URL"); + const char *key = config_get_string(main->Config(), "OutputTemp", + "Key"); + int videoBitrate = config_get_uint(main->Config(), "OutputTemp", + "VBitrate"); + int audioBitrate = config_get_uint(main->Config(), "OutputTemp", + "ABitrate"); + + ui->streamURL->setText(QT_UTF8(url)); + ui->streamKey->setText(QT_UTF8(key)); + ui->streamVBitrate->setValue(videoBitrate); + ui->streamABitrate->setValue(audioBitrate); + + loading = false; +} + static inline void LoadListValue(QComboBox *widget, const char *text, const char *val) { @@ -409,8 +436,8 @@ void OBSBasicSettings::LoadSettings(bool changedOnly) { if (!changedOnly || generalChanged) LoadGeneralSettings(); - //if (!changedOnly || outputChanged) - // LoadOutputSettings(); + if (!changedOnly || outputsChanged) + LoadOutputSettings(); if (!changedOnly || audioChanged) LoadAudioSettings(); if (!changedOnly || videoChanged) @@ -464,6 +491,20 @@ void OBSBasicSettings::SaveVideoSettings() main->ResetVideo(); } +/* TODO: Temporary! */ +void OBSBasicSettings::SaveOutputSettings() +{ + int videoBitrate = ui->streamVBitrate->value(); + int audioBitrate = ui->streamABitrate->value(); + QString url = ui->streamURL->text(); + QString key = ui->streamKey->text(); + + config_set_uint(main->Config(), "OutputTemp", "VBitrate", videoBitrate); + config_set_uint(main->Config(), "OutputTemp", "ABitrate", audioBitrate); + config_set_string(main->Config(), "OutputTemp", "URL", QT_TO_UTF8(url)); + config_set_string(main->Config(), "OutputTemp", "Key", QT_TO_UTF8(key)); +} + static inline QString GetComboData(QComboBox *combo) { int idx = combo->currentIndex(); @@ -517,8 +558,8 @@ void OBSBasicSettings::SaveSettings() { if (generalChanged) SaveGeneralSettings(); - //if (outputChanged) - // SaveOutputSettings(); + if (outputsChanged) + SaveOutputSettings(); if (audioChanged) SaveAudioSettings(); if (videoChanged) @@ -622,6 +663,12 @@ void OBSBasicSettings::GeneralChanged() generalChanged = true; } +void OBSBasicSettings::OutputsChanged() +{ + if (!loading) + outputsChanged = true; +} + void OBSBasicSettings::AudioChanged() { if (!loading) diff --git a/obs/window-basic-settings.hpp b/obs/window-basic-settings.hpp index e21725d43..c776dbc5c 100644 --- a/obs/window-basic-settings.hpp +++ b/obs/window-basic-settings.hpp @@ -63,7 +63,7 @@ private: bool QueryChanges(); void LoadGeneralSettings(); - //void LoadOutputSettings(); + void LoadOutputSettings(); void LoadAudioSettings(); void LoadVideoSettings(); void LoadSettings(bool changedOnly); @@ -83,7 +83,7 @@ private: void LoadFPSData(); void SaveGeneralSettings(); - //void SaveOutputSettings(); + void SaveOutputSettings(); void SaveAudioSettings(); void SaveVideoSettings(); void SaveSettings(); @@ -97,6 +97,7 @@ private slots: void GeneralChanged(); void AudioChanged(); void AudioChangedRestart(); + void OutputsChanged(); void VideoChanged(); void VideoChangedResolution(); void VideoChangedRestart(); diff --git a/plugins/obs-ffmpeg/obs-ffmpeg-output.c b/plugins/obs-ffmpeg/obs-ffmpeg-output.c index 78f4bde5f..0ed545bbf 100644 --- a/plugins/obs-ffmpeg/obs-ffmpeg-output.c +++ b/plugins/obs-ffmpeg/obs-ffmpeg-output.c @@ -18,6 +18,9 @@ #include #include #include +#include +#include +#include #include #include @@ -34,6 +37,7 @@ struct ffmpeg_data { AVFormatContext *output; struct SwsContext *swscale; + int video_bitrate; AVPicture dst_picture; AVFrame *vframe; int frame_size; @@ -41,6 +45,7 @@ struct ffmpeg_data { uint64_t start_timestamp; + int audio_bitrate; uint32_t audio_samplerate; enum audio_format audio_format; size_t audio_planes; @@ -50,8 +55,6 @@ struct ffmpeg_data { AVFrame *aframe; int total_samples; - pthread_mutex_t write_mutex; - const char *filename_test; bool initialized; @@ -61,6 +64,17 @@ struct ffmpeg_output { obs_output_t output; volatile bool active; struct ffmpeg_data ff_data; + + bool connecting; + pthread_t start_thread; + + bool write_thread_active; + pthread_mutex_t write_mutex; + pthread_t write_thread; + sem_t write_sem; + event_t stop_event; + + DARRAY(AVPacket) packets; }; /* ------------------------------------------------------------------------- */ @@ -127,8 +141,10 @@ static bool open_video_codec(struct ffmpeg_data *data) AVCodecContext *context = data->video->codec; int ret; - if (data->vcodec->id == AV_CODEC_ID_H264) + if (data->vcodec->id == AV_CODEC_ID_H264) { av_opt_set(context->priv_data, "preset", "veryfast", 0); + av_opt_set(context->priv_data, "x264-params", "nal-hrd=cbr", 0); + } ret = avcodec_open2(context, data->vcodec, NULL); if (ret < 0) { @@ -188,15 +204,17 @@ static bool create_video_stream(struct ffmpeg_data *data) data->output->oformat->video_codec)) return false; - context = data->video->codec; - context->codec_id = data->output->oformat->video_codec; - context->bit_rate = 6000000; - context->width = ovi.output_width; - context->height = ovi.output_height; - context->time_base.num = ovi.fps_den; - context->time_base.den = ovi.fps_num; - context->gop_size = 12; - context->pix_fmt = AV_PIX_FMT_YUV420P; + context = data->video->codec; + context->codec_id = data->output->oformat->video_codec; + context->bit_rate = data->video_bitrate * 1000; + context->rc_buffer_size = data->video_bitrate * 1000; + context->rc_max_rate = data->video_bitrate * 1000; + context->width = ovi.output_width; + context->height = ovi.output_height; + context->time_base.num = ovi.fps_den; + context->time_base.den = ovi.fps_num; + context->gop_size = 120; + context->pix_fmt = AV_PIX_FMT_YUV420P; if (data->output->oformat->flags & AVFMT_GLOBALHEADER) context->flags |= CODEC_FLAG_GLOBAL_HEADER; @@ -259,7 +277,7 @@ static bool create_audio_stream(struct ffmpeg_data *data) return false; context = data->audio->codec; - context->bit_rate = 128000; + context->bit_rate = data->audio_bitrate * 1000; context->channels = get_audio_channels(aoi.speakers); context->sample_rate = aoi.samples_per_sec; context->sample_fmt = data->acodec->sample_fmts ? @@ -335,7 +353,6 @@ static void close_audio(struct ffmpeg_data *data) static void ffmpeg_data_free(struct ffmpeg_data *data) { - pthread_mutex_lock(&data->write_mutex); if (data->initialized) av_write_trailer(data->output); @@ -348,30 +365,30 @@ static void ffmpeg_data_free(struct ffmpeg_data *data) avformat_free_context(data->output); - pthread_mutex_unlock(&data->write_mutex); - pthread_mutex_destroy(&data->write_mutex); - memset(data, 0, sizeof(struct ffmpeg_data)); } static bool ffmpeg_data_init(struct ffmpeg_data *data, const char *filename) { - memset(data, 0, sizeof(struct ffmpeg_data)); - pthread_mutex_init_value(&data->write_mutex); + bool is_rtmp = false; + memset(data, 0, sizeof(struct ffmpeg_data)); data->filename_test = filename; if (!filename || !*filename) return false; - if (pthread_mutex_init(&data->write_mutex, NULL) != 0) - return false; - av_register_all(); + avformat_network_init(); + + is_rtmp = (astrcmp_n(filename, "rtmp://", 7) == 0); /* TODO: settings */ - avformat_alloc_output_context2(&data->output, NULL, NULL, - data->filename_test); + avformat_alloc_output_context2(&data->output, NULL, + is_rtmp ? "flv" : NULL, data->filename_test); + data->output->oformat->video_codec = AV_CODEC_ID_H264; + data->output->oformat->audio_codec = AV_CODEC_ID_AAC; + if (!data->output) { blog(LOG_WARNING, "Couldn't create avformat context"); goto fail; @@ -382,6 +399,8 @@ static bool ffmpeg_data_init(struct ffmpeg_data *data, const char *filename) if (!open_output_file(data)) goto fail; + av_dump_format(data->output, 0, NULL, 1); + data->initialized = true; return true; @@ -411,21 +430,46 @@ static void ffmpeg_log_callback(void *param, int level, const char *format, static void *ffmpeg_output_create(obs_data_t settings, obs_output_t output) { struct ffmpeg_output *data = bzalloc(sizeof(struct ffmpeg_output)); + pthread_mutex_init_value(&data->write_mutex); data->output = output; + if (pthread_mutex_init(&data->write_mutex, NULL) != 0) + goto fail; + if (event_init(&data->stop_event, EVENT_TYPE_AUTO) != 0) + goto fail; + if (sem_init(&data->write_sem, 0, 0) != 0) + goto fail; + + signal_handler_add(obs_output_signalhandler(output), + "void connect(ptr output, bool success)"); + av_log_set_callback(ffmpeg_log_callback); UNUSED_PARAMETER(settings); return data; + +fail: + pthread_mutex_destroy(&data->write_mutex); + event_destroy(data->stop_event); + bfree(data); + return NULL; } +static void ffmpeg_output_stop(void *data); + static void ffmpeg_output_destroy(void *data) { struct ffmpeg_output *output = data; if (output) { - if (output->active) - ffmpeg_data_free(&output->ff_data); + if (output->connecting) + pthread_join(output->start_thread, NULL); + + ffmpeg_output_stop(output); + + pthread_mutex_destroy(&output->write_mutex); + sem_destroy(&output->write_sem); + event_destroy(output->stop_event); bfree(data); } } @@ -488,9 +532,10 @@ static void receive_video(void *param, const struct video_data *frame) packet.data = data->dst_picture.data[0]; packet.size = sizeof(AVPicture); - pthread_mutex_lock(&data->write_mutex); - ret = av_interleaved_write_frame(data->output, &packet); - pthread_mutex_unlock(&data->write_mutex); + pthread_mutex_lock(&output->write_mutex); + da_push_back(output->packets, &packet); + pthread_mutex_unlock(&output->write_mutex); + sem_post(&output->write_sem); } else { data->vframe->pts = data->total_frames; @@ -511,10 +556,10 @@ static void receive_video(void *param, const struct video_data *frame) context->time_base, data->video->time_base); - pthread_mutex_lock(&data->write_mutex); - ret = av_interleaved_write_frame(data->output, - &packet); - pthread_mutex_unlock(&data->write_mutex); + pthread_mutex_lock(&output->write_mutex); + da_push_back(output->packets, &packet); + pthread_mutex_unlock(&output->write_mutex); + sem_post(&output->write_sem); } else { ret = 0; } @@ -528,20 +573,22 @@ static void receive_video(void *param, const struct video_data *frame) data->total_frames++; } -static inline void encode_audio(struct ffmpeg_data *output, +static inline void encode_audio(struct ffmpeg_output *output, struct AVCodecContext *context, size_t block_size) { + struct ffmpeg_data *data = &output->ff_data; + AVPacket packet = {0}; int ret, got_packet; - size_t total_size = output->frame_size * block_size * context->channels; + size_t total_size = data->frame_size * block_size * context->channels; - output->aframe->nb_samples = output->frame_size; - output->aframe->pts = av_rescale_q(output->total_samples, + data->aframe->nb_samples = data->frame_size; + data->aframe->pts = av_rescale_q(data->total_samples, (AVRational){1, context->sample_rate}, context->time_base); - ret = avcodec_fill_audio_frame(output->aframe, context->channels, - context->sample_fmt, output->samples[0], + ret = avcodec_fill_audio_frame(data->aframe, context->channels, + context->sample_fmt, data->samples[0], (int)total_size, 1); if (ret < 0) { blog(LOG_WARNING, "receive_audio: avcodec_fill_audio_frame " @@ -549,9 +596,9 @@ static inline void encode_audio(struct ffmpeg_data *output, return; } - output->total_samples += output->frame_size; + data->total_samples += data->frame_size; - ret = avcodec_encode_audio2(context, &packet, output->aframe, + ret = avcodec_encode_audio2(context, &packet, data->aframe, &got_packet); if (ret < 0) { blog(LOG_WARNING, "receive_audio: Error encoding audio: %s", @@ -562,18 +609,16 @@ static inline void encode_audio(struct ffmpeg_data *output, if (!got_packet) return; - packet.pts = rescale_ts(packet.pts, context, output->audio); - packet.dts = rescale_ts(packet.dts, context, output->audio); + packet.pts = rescale_ts(packet.pts, context, data->audio); + packet.dts = rescale_ts(packet.dts, context, data->audio); packet.duration = (int)av_rescale_q(packet.duration, context->time_base, - output->audio->time_base); - packet.stream_index = output->audio->index; + data->audio->time_base); + packet.stream_index = data->audio->index; pthread_mutex_lock(&output->write_mutex); - ret = av_interleaved_write_frame(output->output, &packet); - if (ret != 0) - blog(LOG_WARNING, "receive_audio: Error writing audio: %s", - av_err2str(ret)); + da_push_back(output->packets, &packet); pthread_mutex_unlock(&output->write_mutex); + sem_post(&output->write_sem); } static bool prepare_audio(struct ffmpeg_data *data, @@ -627,16 +672,76 @@ static void receive_audio(void *param, const struct audio_data *frame) circlebuf_pop_front(&data->excess_frames[i], data->samples[i], frame_size_bytes); - encode_audio(data, context, data->audio_size); + encode_audio(output, context, data->audio_size); } } -static bool ffmpeg_output_start(void *data) +static bool process_packet(struct ffmpeg_output *output) +{ + AVPacket packet; + bool new_packet = false; + uint64_t time1, time2; + int ret; + + pthread_mutex_lock(&output->write_mutex); + if (output->packets.num) { + packet = output->packets.array[0]; + da_erase(output->packets, 0); + new_packet = true; + } + pthread_mutex_unlock(&output->write_mutex); + + if (!new_packet) + return true; + + time1 = os_gettime_ns(); + + ret = av_interleaved_write_frame(output->ff_data.output, &packet); + if (ret < 0) { + blog(LOG_WARNING, "receive_audio: Error writing packet: %s", + av_err2str(ret)); + + pthread_detach(output->write_thread); + output->write_thread_active = false; + return false; + } + + time2 = os_gettime_ns(); + /*blog(LOG_DEBUG, "%llu, size = %d, flags = %lX, stream = %d, " + "packets queued: %lu: time1; %llu", + time2-time1, packet.size, packet.flags, + packet.stream_index, output->packets.num, time1);*/ + + return true; +} + +static void *write_thread(void *data) { struct ffmpeg_output *output = data; + while (sem_wait(&output->write_sem) == 0) { + /* check to see if shutting down */ + if (event_try(output->stop_event) == 0) + break; + + if (!process_packet(output)) { + ffmpeg_output_stop(output); + break; + } + } + + output->active = false; + return NULL; +} + +static bool try_connect(struct ffmpeg_output *output) +{ video_t video = obs_video(); audio_t audio = obs_audio(); + const char *filename_test; + obs_data_t settings; + int audio_bitrate, video_bitrate; + int ret; if (!video || !audio) { blog(LOG_WARNING, "ffmpeg_output_start: audio and video must " @@ -644,14 +749,18 @@ static bool ffmpeg_output_start(void *data) return false; } - const char *filename_test; - obs_data_t settings = obs_output_get_settings(output->output); + settings = obs_output_get_settings(output->output); filename_test = obs_data_getstring(settings, "filename"); + video_bitrate = (int)obs_data_getint(settings, "video_bitrate"); + audio_bitrate = (int)obs_data_getint(settings, "audio_bitrate"); obs_data_release(settings); if (!filename_test || !*filename_test) return false; + output->ff_data.video_bitrate = video_bitrate; + output->ff_data.audio_bitrate = audio_bitrate; + if (!ffmpeg_data_init(&output->ff_data, filename_test)) return false; @@ -663,21 +772,71 @@ static bool ffmpeg_output_start(void *data) .format = VIDEO_FORMAT_I420 }; - video_output_connect(video, &vsi, receive_video, output); - audio_output_connect(audio, &aci, receive_audio, output); output->active = true; + ret = pthread_create(&output->write_thread, NULL, write_thread, output); + if (ret != 0) { + blog(LOG_WARNING, "ffmpeg_output_start: failed to create write " + "thread."); + ffmpeg_output_stop(output); + return false; + } + + video_output_connect(video, &vsi, receive_video, output); + audio_output_connect(audio, &aci, receive_audio, output); + output->write_thread_active = true; return true; } +static void *start_thread(void *data) +{ + struct ffmpeg_output *output = data; + struct calldata params = {0}; + + bool success = try_connect(output); + + output->connecting = false; + + calldata_setbool(¶ms, "success", success); + calldata_setptr(¶ms, "output", output->output); + signal_handler_signal(obs_output_signalhandler(output->output), + "connect", ¶ms); + calldata_free(¶ms); + + return NULL; +} + +static bool ffmpeg_output_start(void *data) +{ + struct ffmpeg_output *output = data; + int ret; + + if (output->connecting) + return false; + + ret = pthread_create(&output->start_thread, NULL, start_thread, output); + return (output->connecting = (ret == 0)); +} + static void ffmpeg_output_stop(void *data) { struct ffmpeg_output *output = data; if (output->active) { - output->active = false; video_output_disconnect(obs_video(), receive_video, data); audio_output_disconnect(obs_audio(), receive_audio, data); + + if (output->write_thread_active) { + event_signal(output->stop_event); + sem_post(&output->write_sem); + pthread_join(output->write_thread, NULL); + output->write_thread_active = false; + } + + for (size_t i = 0; i < output->packets.num; i++) + av_free_packet(output->packets.array+i); + + da_free(output->packets); ffmpeg_data_free(&output->ff_data); } }