obs-studio/libobs/obs-avc.c
jp9000 0cf9e0cfdd 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
2014-04-01 11:55:18 -07:00

219 lines
5.3 KiB
C

/******************************************************************************
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;
}