Add preliminary FLV/RTMP output (incomplete)

- obs-outputs module:  Add preliminary code to send out data, and add
   an FLV muxer.  This time we don't really need to build the packets
   ourselves, we can just use the FLV muxer and send it directly to
   RTMP_Write and it should automatically parse the entire stream for us
   without us having to do much manual code at all.  We'll see how it
   goes.

 - libobs:  Add AVC NAL packet parsing code

 - libobs/media-io:  Add quick helper functions for audio/video to get
   the width/height/fps/samplerate/etc rather than having to query the
   info structures each time.

 - libobs (obs-output.c):  Change 'connect' signal to 'start' and 'stop'
   signals.  'start' now specifies an error code rather than whether it
   simply failed, that way the client can actually know *why* a failure
   occurred.  Added those error codes to obs-defs.h.

 - libobs:  Add a few functions to duplicate/free encoder packets
This commit is contained in:
jp9000
2014-04-01 11:55:18 -07:00
parent ed6fc7b122
commit 0cf9e0cfdd
29 changed files with 1083 additions and 63 deletions

View File

@@ -122,6 +122,7 @@ set(libobs_mediaio_HEADERS
media-io/video-scaler.h)
set(libobs_util_SOURCES
util/array-serializer.c
util/base.c
util/platform.c
util/cf-lexer.c
@@ -133,6 +134,7 @@ set(libobs_util_SOURCES
util/text-lookup.c
util/cf-parser.c)
set(libobs_util_HEADERS
util/array-serializer.h
util/utf8.h
util/base.h
util/text-lookup.h
@@ -154,6 +156,7 @@ set(libobs_util_HEADERS
set(libobs_libobs_SOURCES
${libobs_PLATFORM_SOURCES}
obs-avc.c
obs-encoder.c
obs-source.c
obs-output.c
@@ -167,6 +170,7 @@ set(libobs_libobs_SOURCES
obs-video.c)
set(libobs_libobs_HEADERS
obs-defs.h
obs-avc.h
obs-encoder.h
obs-service.h
obs-internal.h

View File

@@ -704,6 +704,11 @@ size_t audio_output_channels(audio_t audio)
return audio ? audio->channels : 0;
}
uint32_t audio_output_samplerate(audio_t audio)
{
return audio ? audio->info.samples_per_sec : 0;
}
/* TODO: Optimization of volume multiplication functions */
static inline void mul_vol_u8bit(void *array, float volume, size_t total_num)

View File

@@ -185,12 +185,14 @@ EXPORT bool audio_output_active(audio_t audio);
EXPORT size_t audio_output_blocksize(audio_t audio);
EXPORT size_t audio_output_planes(audio_t audio);
EXPORT size_t audio_output_channels(audio_t audio);
EXPORT uint32_t audio_output_samplerate(audio_t audio);
EXPORT const struct audio_output_info *audio_output_getinfo(audio_t audio);
EXPORT audio_line_t audio_output_createline(audio_t audio, const char *name);
EXPORT void audio_line_destroy(audio_line_t line);
EXPORT void audio_line_output(audio_line_t line, const struct audio_data *data);
#ifdef __cplusplus
}
#endif

View File

@@ -370,3 +370,21 @@ void video_output_stop(video_t video)
os_event_signal(video->update_event);
}
}
uint32_t video_output_width(video_t video)
{
return video ? video->info.width : 0;
}
uint32_t video_output_height(video_t video)
{
return video ? video->info.height : 0;
}
double video_output_framerate(video_t video)
{
if (!video)
return 0.0;
return (double)video->info.fps_num / (double)video->info.fps_den;
}

View File

@@ -133,6 +133,11 @@ EXPORT uint64_t video_getframetime(video_t video);
EXPORT uint64_t video_gettime(video_t video);
EXPORT void video_output_stop(video_t video);
EXPORT uint32_t video_output_width(video_t video);
EXPORT uint32_t video_output_height(video_t video);
EXPORT double video_output_framerate(video_t video);
#ifdef __cplusplus
}
#endif

218
libobs/obs-avc.c Normal file
View File

@@ -0,0 +1,218 @@
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#include "obs.h"
#include "util/array-serializer.h"
enum {
NAL_UNKNOWN = 0,
NAL_SLICE = 1,
NAL_SLICE_DPA = 2,
NAL_SLICE_DPB = 3,
NAL_SLICE_DPC = 4,
NAL_SLICE_IDR = 5,
NAL_SEI = 6,
NAL_SPS = 7,
NAL_PPS = 8,
NAL_AUD = 9,
NAL_FILLER = 12,
};
enum {
NAL_PRIORITY_DISPOSABLE = 0,
NAL_PRIORITY_LOW = 1,
NAL_PRIORITY_HIGH = 2,
NAL_PRIORITY_HIGHEST = 3,
};
/* NOTE: I noticed that FFmpeg does some unusual special handling of certain
* scenarios that I was unaware of, so instead of just searching for {0, 0, 1}
* we'll just use the code from FFmpeg - http://www.ffmpeg.org/ */
static const uint8_t *ff_avc_find_startcode_internal(const uint8_t *p,
const uint8_t *end)
{
const uint8_t *a = p + 4 - ((intptr_t)p & 3);
for (end -= 3; p < a && p < end; p++) {
if (p[0] == 0 && p[1] == 0 && p[2] == 1)
return p;
}
for (end -= 3; p < end; p += 4) {
uint32_t x = *(const uint32_t*)p;
if ((x - 0x01010101) & (~x) & 0x80808080) {
if (p[1] == 0) {
if (p[0] == 0 && p[2] == 1)
return p;
if (p[2] == 0 && p[3] == 1)
return p+1;
}
if (p[3] == 0) {
if (p[2] == 0 && p[4] == 1)
return p+2;
if (p[4] == 0 && p[5] == 1)
return p+3;
}
}
}
for (end += 3; p < end; p++) {
if (p[0] == 0 && p[1] == 0 && p[2] == 1)
return p;
}
return end + 3;
}
const uint8_t *obs_avc_find_startcode(const uint8_t *p, const uint8_t *end)
{
const uint8_t *out= ff_avc_find_startcode_internal(p, end);
if (p < out && out < end && !out[-1]) out--;
return out;
}
static inline int get_drop_priority(int priority)
{
switch (priority) {
case NAL_PRIORITY_DISPOSABLE: return NAL_PRIORITY_DISPOSABLE;
case NAL_PRIORITY_LOW: return NAL_PRIORITY_LOW;
}
return NAL_PRIORITY_HIGHEST;
}
static void serialize_avc_data(struct serializer *s, const uint8_t *data,
size_t size, bool *is_keyframe, int *priority)
{
const uint8_t *nal_start, *nal_end;
const uint8_t *end = data+size;
int type;
nal_start = obs_avc_find_startcode(data, end);
while (true) {
while (nal_start < end && !*(nal_start++));
if (nal_start == end)
break;
type = nal_start[0] & 0x1F;
if (type == NAL_SLICE_IDR || type == NAL_SLICE) {
if (is_keyframe)
*is_keyframe = (type == NAL_SLICE_IDR);
if (priority)
*priority = nal_start[0] >> 5;
}
nal_end = obs_avc_find_startcode(nal_start, end);
s_wb32(s, (uint32_t)(nal_end - nal_start));
s_write(s, nal_start, nal_end - nal_start);
nal_start = nal_end;
}
}
void obs_create_avc_packet(struct encoder_packet *avc_packet,
struct encoder_packet *src)
{
struct array_output_data output;
struct serializer s;
array_output_serializer_init(&s, &output);
*avc_packet = *src;
serialize_avc_data(&s, src->data, src->size, &avc_packet->keyframe,
&avc_packet->priority);
avc_packet->data = output.bytes.array;
avc_packet->size = output.bytes.num;
avc_packet->drop_priority = get_drop_priority(avc_packet->priority);
}
static inline bool has_start_code(const uint8_t *data)
{
if (data[0] != 0 || data[1] != 0)
return false;
return data[2] == 1 || (data[2] == 0 && data[3] == 1);
}
static void get_sps_pps(const uint8_t *data, size_t size,
const uint8_t **sps, size_t *sps_size,
const uint8_t **pps, size_t *pps_size)
{
const uint8_t *nal_start, *nal_end;
const uint8_t *end = data+size;
int type;
nal_start = obs_avc_find_startcode(data, end);
while (true) {
while (nal_start < end && !*(nal_start++));
if (nal_start == end)
break;
nal_end = obs_avc_find_startcode(nal_start, end);
type = nal_start[0] & 0x1F;
if (type == NAL_SPS) {
*sps = nal_start;
*sps_size = nal_end - nal_start;
} else if (type == NAL_PPS) {
*pps = nal_start;
*pps_size = nal_end - nal_start;
}
nal_start = nal_end;
}
}
size_t obs_create_avc_header(uint8_t **header, const uint8_t *data, size_t size)
{
struct array_output_data output;
struct serializer s;
const uint8_t *sps = NULL, *pps = NULL;
size_t sps_size = 0, pps_size = 0;
array_output_serializer_init(&s, &output);
if (size <= 6) return 0;
if (!has_start_code(data)) {
*header = bmemdup(data, size);
return size;
}
get_sps_pps(data, size, &sps, &sps_size, &pps, &pps_size);
if (!sps || !pps || sps_size < 4)
return 0;
s_w8(&s, 0x01);
s_write(&s, sps+1, 3);
s_w8(&s, 0xff);
s_w8(&s, 0xe1);
s_wb16(&s, (uint16_t)sps_size);
s_write(&s, sps, sps_size);
s_w8(&s, 0x01);
s_wb16(&s, (uint16_t)pps_size);
s_write(&s, pps, pps_size);
*header = output.bytes.array;
return output.bytes.num;
}

29
libobs/obs-avc.h Normal file
View File

@@ -0,0 +1,29 @@
/******************************************************************************
Copyright (C) 2014 by Hugh Bailey <obs.jim@gmail.com>
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 <http://www.gnu.org/licenses/>.
******************************************************************************/
#pragma once
struct encoder_packet;
/* Helpers for parsing AVC NAL units. */
const uint8_t *obs_avc_find_startcode(const uint8_t *p, const uint8_t *end);
EXPORT void obs_create_avc_packet(struct encoder_packet *avc_packet,
struct encoder_packet *src);
EXPORT size_t obs_create_avc_header(uint8_t **header, const uint8_t *data,
size_t size);

View File

@@ -1,5 +1,5 @@
/******************************************************************************
Copyright (C) 2013 by Hugh Bailey <obs.jim@gmail.com>
Copyright (C) 2013-2014 by Hugh Bailey <obs.jim@gmail.com>
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
@@ -25,3 +25,8 @@
#define MODULE_FILE_NOT_FOUND -2
#define MODULE_FUNCTION_NOT_FOUND -3
#define MODULE_INCOMPATIBLE_VER -4
#define OBS_OUTPUT_SUCCESS 0
#define OBS_OUTPUT_BAD_PATH -1
#define OBS_OUTPUT_CONNECT_FAILED -2
#define OBS_OUTPUT_INVALID_STREAM -3

View File

@@ -358,6 +358,11 @@ void obs_encoder_stop(obs_encoder_t encoder,
}
}
const char *obs_encoder_get_codec(obs_encoder_t encoder)
{
return encoder ? encoder->info.codec : NULL;
}
video_t obs_encoder_video(obs_encoder_t encoder)
{
return (encoder && encoder->info.type == OBS_ENCODER_VIDEO) ?
@@ -526,3 +531,16 @@ void obs_encoder_remove_output(struct obs_encoder *encoder,
da_erase_item(encoder->outputs, &output);
pthread_mutex_unlock(&encoder->outputs_mutex);
}
void obs_duplicate_encoder_packet(struct encoder_packet *dst,
const struct encoder_packet *src)
{
*dst = *src;
dst->data = bmemdup(src->data, src->size);
}
void obs_free_encoder_packet(struct encoder_packet *packet)
{
bfree(packet->data);
memset(packet, 0, sizeof(struct encoder_packet));
}

View File

@@ -36,6 +36,9 @@ struct encoder_packet {
enum obs_encoder_type type; /**< Encoder type */
/* ---------------------------------------------------------------- */
/* Internal video variables (will be parsed automatically) */
bool keyframe; /**< Is a keyframe */
/**

View File

@@ -28,6 +28,26 @@ static inline const struct obs_output_info *find_output(const char *id)
return NULL;
}
static const char *output_signals[] = {
"void start(ptr output, int errorcode)",
"void stop(ptr output)",
NULL
};
static bool init_output_handlers(struct obs_output *output)
{
output->signals = signal_handler_create();
if (!output->signals)
return false;
output->procs = proc_handler_create();
if (!output->procs)
return false;
signal_handler_add_array(output->signals, output_signals);
return true;
}
obs_output_t obs_output_create(const char *id, const char *name,
obs_data_t settings)
{
@@ -41,12 +61,7 @@ obs_output_t obs_output_create(const char *id, const char *name,
output = bzalloc(sizeof(struct obs_output));
output->signals = signal_handler_create();
if (!output->signals)
goto fail;
output->procs = proc_handler_create();
if (!output->procs)
if (!init_output_handlers(output))
goto fail;
output->info = *info;
@@ -327,6 +342,23 @@ static void hook_data_capture(struct obs_output *output, bool encoded,
}
}
static inline void signal_start(struct obs_output *output, int code)
{
struct calldata params = {0};
calldata_setint(&params, "code", code);
calldata_setptr(&params, "output", output);
signal_handler_signal(output->signals, "start", &params);
calldata_free(&params);
}
static inline void signal_stop(struct obs_output *output)
{
struct calldata params = {0};
calldata_setptr(&params, "output", output);
signal_handler_signal(output->signals, "stop", &params);
calldata_free(&params);
}
static inline void convert_flags(struct obs_output *output, uint32_t flags,
bool *encoded, bool *has_video, bool *has_audio)
{
@@ -340,6 +372,18 @@ static inline void convert_flags(struct obs_output *output, uint32_t flags,
*has_audio = (flags & OBS_OUTPUT_AUDIO) != 0;
}
bool obs_output_can_begin_data_capture(obs_output_t output, uint32_t flags)
{
bool encoded, has_video, has_audio;
if (!output) return false;
if (output->active) return false;
convert_flags(output, flags, &encoded, &has_video, &has_audio);
return can_begin_data_capture(output, encoded, has_video, has_audio);
}
bool obs_output_begin_data_capture(obs_output_t output, uint32_t flags)
{
bool encoded, has_video, has_audio;
@@ -354,6 +398,7 @@ bool obs_output_begin_data_capture(obs_output_t output, uint32_t flags)
hook_data_capture(output, encoded, has_video, has_audio);
output->active = true;
signal_start(output, OBS_OUTPUT_SUCCESS);
return true;
}
@@ -387,4 +432,10 @@ void obs_output_end_data_capture(obs_output_t output)
}
output->active = false;
signal_stop(output);
}
void obs_output_signal_start_fail(obs_output_t output, int code)
{
signal_start(output, code);
}

View File

@@ -21,6 +21,7 @@
#define OBS_OUTPUT_AUDIO (1<<1)
#define OBS_OUTPUT_AV (OBS_OUTPUT_VIDEO | OBS_OUTPUT_AUDIO)
#define OBS_OUTPUT_ENCODED (1<<2)
#define OBS_OUTPUT_SERVICE (1<<3)
struct encoder_packet;

View File

@@ -22,15 +22,15 @@ struct obs_service_info {
char *id;
const char *(*getname)(const char *locale);
#if 0
void *(*create)(obs_data_t settings, struct service_data *service);
void *(*create)(obs_data_t settings, obs_service_t service);
void (*destroy)(void *data);
/* optional */
void (*update)(void *data, obs_data_t settings);
/* get stream url/key */
/* get (viewers/etc) */
const char *(*get_url)(void *data);
const char *(*get_key)(void *data);
/* send (current game/title/activate commercial/etc) */
#endif
};

View File

@@ -747,6 +747,10 @@ EXPORT void obs_output_set_video_conversion(obs_output_t output,
EXPORT void obs_output_set_audio_conversion(obs_output_t output,
const struct audio_convert_info *conversion);
/** Returns whether data capture can begin with the specified flags */
EXPORT bool obs_output_can_begin_data_capture(obs_output_t output,
uint32_t flags);
/**
* Begins data capture from media/encoders.
*
@@ -764,6 +768,9 @@ EXPORT bool obs_output_begin_data_capture(obs_output_t output, uint32_t flags);
/** Ends data capture from media/encoders */
EXPORT void obs_output_end_data_capture(obs_output_t output);
/** Signals that start failed */
EXPORT void obs_output_signal_start_fail(obs_output_t output, int code);
/* ------------------------------------------------------------------------- */
/* Encoders */
@@ -825,6 +832,9 @@ EXPORT void obs_encoder_stop(obs_encoder_t encoder,
void (*new_packet)(void *param, struct encoder_packet *packet),
void *param);
/** Returns the codec of the encoder */
EXPORT const char *obs_encoder_get_codec(obs_encoder_t encoder);
/** Gets the default settings for an encoder type */
EXPORT obs_data_t obs_encoder_defaults(const char *id);
@@ -864,6 +874,12 @@ EXPORT video_t obs_encoder_video(obs_encoder_t encoder);
*/
EXPORT audio_t obs_encoder_audio(obs_encoder_t encoder);
/** Duplicates an encoder packet */
EXPORT void obs_duplicate_encoder_packet(struct encoder_packet *dst,
const struct encoder_packet *src);
EXPORT void obs_free_encoder_packet(struct encoder_packet *packet);
/* ------------------------------------------------------------------------- */
/* Stream Services */