ad3d448f19
Uses the sample rate the audio encoder is running at for ffmpeg muxing (in case the audio sample rate had to be changed by the encoder)
264 lines
6.6 KiB
C
264 lines
6.6 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 <stdio.h>
|
|
#include <util/dstr.h>
|
|
#include <util/array-serializer.h>
|
|
#include "flv-mux.h"
|
|
#include "obs-output-ver.h"
|
|
#include "rtmp-helpers.h"
|
|
|
|
/* TODO: FIXME: this is currently hard-coded to h264 and aac! ..not that we'll
|
|
* use anything else for a long time. */
|
|
|
|
//#define DEBUG_TIMESTAMPS
|
|
//#define WRITE_FLV_HEADER
|
|
|
|
#define VIDEO_HEADER_SIZE 5
|
|
|
|
static inline double encoder_bitrate(obs_encoder_t *encoder)
|
|
{
|
|
obs_data_t *settings = obs_encoder_get_settings(encoder);
|
|
double bitrate = obs_data_get_double(settings, "bitrate");
|
|
|
|
obs_data_release(settings);
|
|
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 bool build_flv_meta_data(obs_output_t *context,
|
|
uint8_t **output, size_t *size, size_t a_idx)
|
|
{
|
|
obs_encoder_t *vencoder = obs_output_get_video_encoder(context);
|
|
obs_encoder_t *aencoder = obs_output_get_audio_encoder(context, a_idx);
|
|
video_t *video = obs_encoder_video(vencoder);
|
|
audio_t *audio = obs_encoder_audio(aencoder);
|
|
char buf[4096];
|
|
char *enc = buf;
|
|
char *end = enc+sizeof(buf);
|
|
struct dstr encoder_name = {0};
|
|
|
|
if (a_idx > 0 && !aencoder)
|
|
return false;
|
|
|
|
enc_str(&enc, end, "onMetaData");
|
|
|
|
*enc++ = AMF_ECMA_ARRAY;
|
|
enc = AMF_EncodeInt32(enc, end, a_idx == 0 ? 14 : 9);
|
|
|
|
enc_num_val(&enc, end, "duration", 0.0);
|
|
enc_num_val(&enc, end, "fileSize", 0.0);
|
|
|
|
if (a_idx == 0) {
|
|
enc_num_val(&enc, end, "width",
|
|
(double)obs_encoder_get_width(vencoder));
|
|
enc_num_val(&enc, end, "height",
|
|
(double)obs_encoder_get_height(vencoder));
|
|
|
|
enc_str_val(&enc, end, "videocodecid", "avc1");
|
|
enc_num_val(&enc, end, "videodatarate",
|
|
encoder_bitrate(vencoder));
|
|
enc_num_val(&enc, end, "framerate",
|
|
video_output_get_frame_rate(video));
|
|
}
|
|
|
|
enc_str_val(&enc, end, "audiocodecid", "mp4a");
|
|
enc_num_val(&enc, end, "audiodatarate", encoder_bitrate(aencoder));
|
|
enc_num_val(&enc, end, "audiosamplerate",
|
|
(double)obs_encoder_get_sample_rate(aencoder));
|
|
enc_num_val(&enc, end, "audiosamplesize", 16.0);
|
|
enc_num_val(&enc, end, "audiochannels",
|
|
(double)audio_output_get_channels(audio));
|
|
|
|
enc_bool_val(&enc, end, "stereo",
|
|
audio_output_get_channels(audio) == 2);
|
|
|
|
dstr_printf(&encoder_name, "%s (libobs version ",
|
|
MODULE_NAME);
|
|
|
|
#ifdef HAVE_OBSCONFIG_H
|
|
dstr_cat(&encoder_name, OBS_VERSION);
|
|
#else
|
|
dstr_catf(&encoder_name, "%d.%d.%d",
|
|
LIBOBS_API_MAJOR_VER,
|
|
LIBOBS_API_MINOR_VER,
|
|
LIBOBS_API_PATCH_VER);
|
|
#endif
|
|
|
|
dstr_cat(&encoder_name, ")");
|
|
|
|
enc_str_val(&enc, end, "encoder", encoder_name.array);
|
|
dstr_free(&encoder_name);
|
|
|
|
*enc++ = 0;
|
|
*enc++ = 0;
|
|
*enc++ = AMF_OBJECT_END;
|
|
|
|
*size = enc-buf;
|
|
*output = bmemdup(buf, *size);
|
|
return true;
|
|
}
|
|
|
|
bool flv_meta_data(obs_output_t *context, uint8_t **output, size_t *size,
|
|
bool write_header, size_t audio_idx)
|
|
{
|
|
struct array_output_data data;
|
|
struct serializer s;
|
|
uint8_t *meta_data = NULL;
|
|
size_t meta_data_size;
|
|
uint32_t start_pos;
|
|
|
|
array_output_serializer_init(&s, &data);
|
|
|
|
if (!build_flv_meta_data(context, &meta_data, &meta_data_size,
|
|
audio_idx)) {
|
|
bfree(meta_data);
|
|
return false;
|
|
}
|
|
|
|
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);
|
|
|
|
s_w8(&s, RTMP_PACKET_TYPE_INFO);
|
|
|
|
s_wb24(&s, (uint32_t)meta_data_size);
|
|
s_wb32(&s, 0);
|
|
s_wb24(&s, 0);
|
|
|
|
s_write(&s, meta_data, meta_data_size);
|
|
|
|
s_wb32(&s, (uint32_t)serializer_get_pos(&s) - start_pos + 4 - 1);
|
|
|
|
*output = data.bytes.array;
|
|
*size = data.bytes.num;
|
|
|
|
bfree(meta_data);
|
|
return true;
|
|
}
|
|
|
|
#ifdef DEBUG_TIMESTAMPS
|
|
static int32_t last_time = 0;
|
|
#endif
|
|
|
|
static void flv_video(struct serializer *s, struct encoder_packet *packet,
|
|
bool is_header)
|
|
{
|
|
int64_t offset = packet->pts - packet->dts;
|
|
int32_t time_ms = get_ms_time(packet, packet->dts);
|
|
|
|
if (!packet->data || !packet->size)
|
|
return;
|
|
|
|
s_w8(s, RTMP_PACKET_TYPE_VIDEO);
|
|
|
|
#ifdef DEBUG_TIMESTAMPS
|
|
blog(LOG_DEBUG, "Video: %lu", time_ms);
|
|
|
|
if (last_time > time_ms)
|
|
blog(LOG_DEBUG, "Non-monotonic");
|
|
|
|
last_time = time_ms;
|
|
#endif
|
|
|
|
s_wb24(s, (uint32_t)packet->size + 5);
|
|
s_wb24(s, time_ms);
|
|
s_w8(s, (time_ms >> 24) & 0x7F);
|
|
s_wb24(s, 0);
|
|
|
|
/* these are the 5 extra bytes mentioned above */
|
|
s_w8(s, packet->keyframe ? 0x17 : 0x27);
|
|
s_w8(s, is_header ? 0 : 1);
|
|
s_wb24(s, get_ms_time(packet, offset));
|
|
s_write(s, packet->data, packet->size);
|
|
|
|
/* write tag size (starting byte doesnt count) */
|
|
s_wb32(s, (uint32_t)serializer_get_pos(s) + 4 - 1);
|
|
}
|
|
|
|
static void flv_audio(struct serializer *s, struct encoder_packet *packet,
|
|
bool is_header)
|
|
{
|
|
int32_t time_ms = get_ms_time(packet, packet->dts);
|
|
|
|
if (!packet->data || !packet->size)
|
|
return;
|
|
|
|
s_w8(s, RTMP_PACKET_TYPE_AUDIO);
|
|
|
|
#ifdef DEBUG_TIMESTAMPS
|
|
blog(LOG_DEBUG, "Audio: %lu", time_ms);
|
|
|
|
if (last_time > time_ms)
|
|
blog(LOG_DEBUG, "Non-monotonic");
|
|
|
|
last_time = time_ms;
|
|
#endif
|
|
|
|
s_wb24(s, (uint32_t)packet->size + 2);
|
|
s_wb24(s, time_ms);
|
|
s_w8(s, (time_ms >> 24) & 0x7F);
|
|
s_wb24(s, 0);
|
|
|
|
/* these are the two extra bytes mentioned above */
|
|
s_w8(s, 0xaf);
|
|
s_w8(s, is_header ? 0 : 1);
|
|
s_write(s, packet->data, packet->size);
|
|
|
|
/* write tag size (starting byte doesnt count) */
|
|
s_wb32(s, (uint32_t)serializer_get_pos(s) + 4 - 1);
|
|
}
|
|
|
|
void flv_packet_mux(struct encoder_packet *packet,
|
|
uint8_t **output, size_t *size, bool is_header)
|
|
{
|
|
struct array_output_data data;
|
|
struct serializer s;
|
|
|
|
array_output_serializer_init(&s, &data);
|
|
|
|
if (packet->type == OBS_ENCODER_VIDEO)
|
|
flv_video(&s, packet, is_header);
|
|
else
|
|
flv_audio(&s, packet, is_header);
|
|
|
|
*output = data.bytes.array;
|
|
*size = data.bytes.num;
|
|
}
|