diff --git a/libobs/util/platform.c b/libobs/util/platform.c index d5a9d4356..6f02feede 100644 --- a/libobs/util/platform.c +++ b/libobs/util/platform.c @@ -14,6 +14,8 @@ * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ +#define _FILE_OFFSET_BITS 64 + #include #include #include "c99defs.h" @@ -56,38 +58,56 @@ FILE *os_fopen(const char *path, const char *mode) #endif } -off_t os_fgetsize(FILE *file) +int64_t os_fgetsize(FILE *file) { - off_t cur_offset = ftello(file); - off_t size; + int64_t cur_offset = os_ftelli64(file); + int64_t size; int errval = 0; - if (fseeko(file, 0, SEEK_END) == -1) + if (fseek(file, 0, SEEK_END) == -1) return -1; - size = ftello(file); + size = os_ftelli64(file); if (size == -1) errval = errno; - if (fseeko(file, cur_offset, SEEK_SET) != 0 && errval != 0) + if (os_fseeki64(file, cur_offset, SEEK_SET) != 0 && errval != 0) errno = errval; return size; } +int os_fseeki64(FILE *file, int64_t offset, int origin) +{ +#ifdef _MSC_VER + return _fseeki64(file, offset, origin); +#else + return fseeko(file, offset, origin); +#endif +} + +int64_t os_ftelli64(FILE *file) +{ +#ifdef _MSC_VER + return _ftelli64(file); +#else + return ftello(file); +#endif +} + size_t os_fread_mbs(FILE *file, char **pstr) { size_t size = 0; size_t len = 0; - fseeko(file, 0, SEEK_END); - size = (size_t)ftello(file); + fseek(file, 0, SEEK_END); + size = (size_t)os_ftelli64(file); *pstr = NULL; if (size > 0) { char *mbstr = bmalloc(size+1); - fseeko(file, 0, SEEK_SET); + fseek(file, 0, SEEK_SET); size = fread(mbstr, 1, size, file); if (size == 0) { bfree(mbstr); @@ -110,8 +130,8 @@ size_t os_fread_utf8(FILE *file, char **pstr) *pstr = NULL; - fseeko(file, 0, SEEK_END); - size = (size_t)ftello(file); + fseek(file, 0, SEEK_END); + size = (size_t)os_ftelli64(file); if (size > 0) { char bom[3]; @@ -119,7 +139,7 @@ size_t os_fread_utf8(FILE *file, char **pstr) off_t offset; /* remove the ghastly BOM if present */ - fseeko(file, 0, SEEK_SET); + fseek(file, 0, SEEK_SET); size_read = fread(bom, 1, 3, file); if (size_read != 3) return 0; @@ -131,7 +151,7 @@ size_t os_fread_utf8(FILE *file, char **pstr) return 0; utf8str = bmalloc(size+1); - fseeko(file, offset, SEEK_SET); + fseek(file, offset, SEEK_SET); size = fread(utf8str, 1, size, file); if (size == 0) { @@ -314,23 +334,3 @@ size_t os_mbs_to_utf8_ptr(const char *str, size_t len, char **pstr) return out_len; } - -#ifdef _MSC_VER -int fseeko(FILE *stream, off_t offset, int whence) -{ -#if _FILE_OFFSET_BITS == 64 - return _fseeki64(stream, offset, whence); -#else - return fseek(stream, offset, whence); -#endif /* _FILE_OFFSET_BITS == 64 */ -} - -off_t ftello(FILE *stream) -{ -#if _FILE_OFFSET_BITS == 64 - return _ftelli64(stream); -#else - return ftell(stream); -#endif /* _FILE_OFFSET_BITS == 64 */ -} -#endif /* _MSC_VER */ diff --git a/libobs/util/platform.h b/libobs/util/platform.h index 107f85916..a27b6f1e3 100644 --- a/libobs/util/platform.h +++ b/libobs/util/platform.h @@ -32,7 +32,10 @@ extern "C" { EXPORT FILE *os_wfopen(const wchar_t *path, const char *mode); EXPORT FILE *os_fopen(const char *path, const char *mode); -EXPORT off_t os_fgetsize(FILE *file); +EXPORT int64_t os_fgetsize(FILE *file); + +EXPORT int os_fseeki64(FILE *file, int64_t offset, int origin); +EXPORT int64_t os_ftelli64(FILE *file); EXPORT size_t os_fread_mbs(FILE *file, char **pstr); EXPORT size_t os_fread_utf8(FILE *file, char **pstr); @@ -97,8 +100,6 @@ EXPORT int os_unlink(const char *path); EXPORT int os_mkdir(const char *path); #ifdef _MSC_VER -EXPORT int fseeko(FILE *stream, off_t offset, int whence); -EXPORT off_t ftello(FILE *stream); #define strtoll _strtoi64 #endif diff --git a/plugins/obs-outputs/CMakeLists.txt b/plugins/obs-outputs/CMakeLists.txt index f212a26cb..4d4c261e6 100644 --- a/plugins/obs-outputs/CMakeLists.txt +++ b/plugins/obs-outputs/CMakeLists.txt @@ -32,10 +32,12 @@ set(obs-outputs_HEADERS obs-output-ver.h rtmp-helpers.h flv-mux.h + flv-output.h librtmp) set(obs-outputs_SOURCES obs-outputs.c rtmp-stream.c + flv-output.c flv-mux.c) add_library(obs-outputs MODULE diff --git a/plugins/obs-outputs/flv-mux.c b/plugins/obs-outputs/flv-mux.c index 6dca870b4..66bab0204 100644 --- a/plugins/obs-outputs/flv-mux.c +++ b/plugins/obs-outputs/flv-mux.c @@ -16,6 +16,7 @@ ******************************************************************************/ #include +#include #include #include "flv-mux.h" #include "obs-output-ver.h" @@ -28,7 +29,6 @@ //#define WRITE_FLV_HEADER #define VIDEO_HEADER_SIZE 5 -#define MILLISECOND_DEN 1000 static inline double encoder_bitrate(obs_encoder_t encoder) { @@ -39,6 +39,22 @@ static inline double encoder_bitrate(obs_encoder_t encoder) return bitrate; } +#define FLV_INFO_SIZE_OFFSET 42 + +void write_file_info(FILE *file, int64_t duration_ms, int64_t size) +{ + char buf[64]; + char *enc = buf; + char *end = enc + sizeof(buf); + + fseek(file, FLV_INFO_SIZE_OFFSET, SEEK_SET); + + enc_num_val(&enc, end, "duration", (double)duration_ms / 1000.0); + enc_num_val(&enc, end, "fileSize", (double)size); + + fwrite(buf, 1, enc - buf, file); +} + static void build_flv_meta_data(obs_output_t context, uint8_t **output, size_t *size) { @@ -83,7 +99,8 @@ static void build_flv_meta_data(obs_output_t context, *output = bmemdup(buf, *size); } -void flv_meta_data(obs_output_t context, uint8_t **output, size_t *size) +void flv_meta_data(obs_output_t context, uint8_t **output, size_t *size, + bool write_header) { struct array_output_data data; struct serializer s; @@ -95,13 +112,13 @@ void flv_meta_data(obs_output_t context, uint8_t **output, size_t *size) build_flv_meta_data(context, &meta_data, &meta_data_size); -#ifdef WRITE_FLV_HEADER - s_write(&s, "FLV", 3); - s_w8(&s, 1); - s_w8(&s, 5); - s_wb32(&s, 9); - s_wb32(&s, 0); -#endif + if (write_header) { + s_write(&s, "FLV", 3); + s_w8(&s, 1); + s_w8(&s, 5); + s_wb32(&s, 9); + s_wb32(&s, 0); + } start_pos = serializer_get_pos(&s); @@ -121,11 +138,6 @@ void flv_meta_data(obs_output_t context, uint8_t **output, size_t *size) bfree(meta_data); } -static uint32_t get_ms_time(struct encoder_packet *packet, int64_t val) -{ - return (uint32_t)(val * MILLISECOND_DEN / packet->timebase_den); -} - #ifdef DEBUG_TIMESTAMPS static int32_t last_time = 0; #endif diff --git a/plugins/obs-outputs/flv-mux.h b/plugins/obs-outputs/flv-mux.h index ce4f9262b..90ed44efd 100644 --- a/plugins/obs-outputs/flv-mux.h +++ b/plugins/obs-outputs/flv-mux.h @@ -19,6 +19,16 @@ #include -extern void flv_meta_data(obs_output_t context, uint8_t **output, size_t *size); +#define MILLISECOND_DEN 1000 + +static uint32_t get_ms_time(struct encoder_packet *packet, int64_t val) +{ + return (uint32_t)(val * MILLISECOND_DEN / packet->timebase_den); +} + +extern void write_file_info(FILE *file, int64_t duration_ms, int64_t size); + +extern void flv_meta_data(obs_output_t context, uint8_t **output, size_t *size, + bool write_header); extern void flv_packet_mux(struct encoder_packet *packet, uint8_t **output, size_t *size, bool is_header); diff --git a/plugins/obs-outputs/flv-output.c b/plugins/obs-outputs/flv-output.c new file mode 100644 index 000000000..0a7e13452 --- /dev/null +++ b/plugins/obs-outputs/flv-output.c @@ -0,0 +1,211 @@ +/****************************************************************************** + Copyright (C) 2014 by Hugh Bailey + + This program is free software: you can redistribute it and/or modify + it under the terms of the GNU General Public License as published by + the Free Software Foundation, either version 2 of the License, or + (at your option) any later version. + + This program is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + GNU General Public License for more details. + + You should have received a copy of the GNU General Public License + along with this program. If not, see . +******************************************************************************/ + +#include +#include +#include +#include +#include +#include +#include +#include "flv-mux.h" + +struct flv_output { + obs_output_t output; + struct dstr path; + FILE *file; + bool active; + int64_t last_packet_ts; +}; + +static const char *flv_output_getname(const char *locale) +{ + UNUSED_PARAMETER(locale); + return "FLV File Output"; +} + +static void flv_output_stop(void *data); + +static void flv_output_destroy(void *data) +{ + struct flv_output *stream = data; + + if (stream->active) + flv_output_stop(data); + + dstr_free(&stream->path); + bfree(stream); +} + +static void *flv_output_create(obs_data_t settings, obs_output_t output) +{ + struct flv_output *stream = bzalloc(sizeof(struct flv_output)); + stream->output = output; + + UNUSED_PARAMETER(settings); + return stream; +} + +static void flv_output_stop(void *data) +{ + struct flv_output *stream = data; + + if (stream->active) { + if (stream->file) + write_file_info(stream->file, stream->last_packet_ts, + os_ftelli64(stream->file)); + + fclose(stream->file); + obs_output_end_data_capture(stream->output); + stream->active = false; + } +} + +static int write_packet(struct flv_output *stream, + struct encoder_packet *packet, bool is_header) +{ + uint8_t *data; + size_t size; + int ret = 0; + + stream->last_packet_ts = get_ms_time(packet, packet->dts); + + flv_packet_mux(packet, &data, &size, is_header); + fwrite(data, 1, size, stream->file); + bfree(data); + obs_free_encoder_packet(packet); + + return ret; +} + +static void write_meta_data(struct flv_output *stream) +{ + uint8_t *meta_data; + size_t meta_data_size; + + flv_meta_data(stream->output, &meta_data, &meta_data_size, true); + fwrite(meta_data, 1, meta_data_size, stream->file); + bfree(meta_data); +} + +static void write_audio_header(struct flv_output *stream) +{ + obs_output_t context = stream->output; + obs_encoder_t aencoder = obs_output_get_audio_encoder(context); + uint8_t *header; + + struct encoder_packet packet = { + .type = OBS_ENCODER_AUDIO, + .timebase_den = 1 + }; + + obs_encoder_get_extra_data(aencoder, &header, &packet.size); + packet.data = bmemdup(header, packet.size); + write_packet(stream, &packet, true); +} + +static void write_video_header(struct flv_output *stream) +{ + obs_output_t context = stream->output; + obs_encoder_t vencoder = obs_output_get_video_encoder(context); + uint8_t *header; + size_t size; + + struct encoder_packet packet = { + .type = OBS_ENCODER_VIDEO, + .timebase_den = 1, + .keyframe = true + }; + + obs_encoder_get_extra_data(vencoder, &header, &size); + packet.size = obs_parse_avc_header(&packet.data, header, size); + write_packet(stream, &packet, true); +} + +static void write_headers(struct flv_output *stream) +{ + write_meta_data(stream); + write_audio_header(stream); + write_video_header(stream); +} + +static bool flv_output_start(void *data) +{ + struct flv_output *stream = data; + obs_data_t settings; + const char *path; + + if (!obs_output_can_begin_data_capture(stream->output, 0)) + return false; + if (!obs_output_initialize_encoders(stream->output, 0)) + return false; + + /* get path */ + settings = obs_output_get_settings(stream->output); + path = obs_data_getstring(settings, "path"); + dstr_copy(&stream->path, path); + obs_data_release(settings); + + stream->file = os_fopen(stream->path.array, "wb"); + if (!stream->file) { + blog(LOG_WARNING, "Unable to open FLV file '%s'", + stream->path.array); + return false; + } + + /* write headers and start capture */ + stream->active = true; + write_headers(stream); + obs_output_begin_data_capture(stream->output, 0); + + return true; +} + +static void flv_output_data(void *data, struct encoder_packet *packet) +{ + struct flv_output *stream = data; + struct encoder_packet parsed_packet; + + if (packet->type == OBS_ENCODER_VIDEO) { + obs_parse_avc_packet(&parsed_packet, packet); + write_packet(stream, &parsed_packet, false); + obs_free_encoder_packet(&parsed_packet); + } else { + write_packet(stream, packet, false); + } +} + +static obs_properties_t flv_output_properties(const char *locale) +{ + obs_properties_t props = obs_properties_create(locale); + + /* TODO: locale */ + obs_properties_add_text(props, "path", "File Path", OBS_TEXT_DEFAULT); + return props; +} + +struct obs_output_info flv_output_info = { + .id = "flv_output", + .flags = OBS_OUTPUT_AV | OBS_OUTPUT_ENCODED, + .getname = flv_output_getname, + .create = flv_output_create, + .destroy = flv_output_destroy, + .start = flv_output_start, + .stop = flv_output_stop, + .encoded_packet = flv_output_data, + .properties = flv_output_properties +}; diff --git a/plugins/obs-outputs/obs-outputs.c b/plugins/obs-outputs/obs-outputs.c index 92be62749..7b2efb957 100644 --- a/plugins/obs-outputs/obs-outputs.c +++ b/plugins/obs-outputs/obs-outputs.c @@ -8,6 +8,7 @@ OBS_DECLARE_MODULE() extern struct obs_output_info rtmp_output_info; +extern struct obs_output_info flv_output_info; bool obs_module_load(uint32_t libobs_ver) { @@ -17,6 +18,7 @@ bool obs_module_load(uint32_t libobs_ver) #endif obs_register_output(&rtmp_output_info); + obs_register_output(&flv_output_info); UNUSED_PARAMETER(libobs_ver); return true; diff --git a/plugins/obs-outputs/rtmp-stream.c b/plugins/obs-outputs/rtmp-stream.c index e57d7012a..154d3905b 100644 --- a/plugins/obs-outputs/rtmp-stream.c +++ b/plugins/obs-outputs/rtmp-stream.c @@ -255,7 +255,7 @@ static void send_meta_data(struct rtmp_stream *stream) uint8_t *meta_data; size_t meta_data_size; - flv_meta_data(stream->output, &meta_data, &meta_data_size); + flv_meta_data(stream->output, &meta_data, &meta_data_size, false); #ifdef FILE_TEST fwrite(meta_data, 1, meta_data_size, stream->test); #else diff --git a/vs/2013/obs-outputs/obs-outputs.vcxproj b/vs/2013/obs-outputs/obs-outputs.vcxproj index 0f4b79bcb..bb44e4346 100644 --- a/vs/2013/obs-outputs/obs-outputs.vcxproj +++ b/vs/2013/obs-outputs/obs-outputs.vcxproj @@ -180,6 +180,7 @@ + diff --git a/vs/2013/obs-outputs/obs-outputs.vcxproj.filters b/vs/2013/obs-outputs/obs-outputs.vcxproj.filters index 151200dc3..b38e8a454 100644 --- a/vs/2013/obs-outputs/obs-outputs.vcxproj.filters +++ b/vs/2013/obs-outputs/obs-outputs.vcxproj.filters @@ -98,5 +98,8 @@ librtmp\Source Files + + Source Files + \ No newline at end of file