Imported Upstream version 0.13.2+dsfg1
This commit is contained in:
commit
fb3990e9e5
2036 changed files with 287360 additions and 0 deletions
501
libobs/media-io/audio-io.c
Normal file
501
libobs/media-io/audio-io.c
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2013 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 <math.h>
|
||||
#include <inttypes.h>
|
||||
|
||||
#include "../util/threading.h"
|
||||
#include "../util/darray.h"
|
||||
#include "../util/circlebuf.h"
|
||||
#include "../util/platform.h"
|
||||
#include "../util/profiler.h"
|
||||
|
||||
#include "audio-io.h"
|
||||
#include "audio-resampler.h"
|
||||
|
||||
extern profiler_name_store_t *obs_get_profiler_name_store(void);
|
||||
|
||||
/* #define DEBUG_AUDIO */
|
||||
|
||||
#define nop() do {int invalid = 0;} while(0)
|
||||
|
||||
struct audio_input {
|
||||
struct audio_convert_info conversion;
|
||||
audio_resampler_t *resampler;
|
||||
|
||||
audio_output_callback_t callback;
|
||||
void *param;
|
||||
};
|
||||
|
||||
static inline void audio_input_free(struct audio_input *input)
|
||||
{
|
||||
audio_resampler_destroy(input->resampler);
|
||||
}
|
||||
|
||||
struct audio_mix {
|
||||
DARRAY(struct audio_input) inputs;
|
||||
float buffer[MAX_AUDIO_CHANNELS][AUDIO_OUTPUT_FRAMES];
|
||||
};
|
||||
|
||||
struct audio_output {
|
||||
struct audio_output_info info;
|
||||
size_t block_size;
|
||||
size_t channels;
|
||||
size_t planes;
|
||||
|
||||
pthread_t thread;
|
||||
os_event_t *stop_event;
|
||||
|
||||
bool initialized;
|
||||
|
||||
audio_input_callback_t input_cb;
|
||||
void *input_param;
|
||||
pthread_mutex_t input_mutex;
|
||||
struct audio_mix mixes[MAX_AUDIO_MIXES];
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
/* the following functions are used to calculate frame offsets based upon
|
||||
* timestamps. this will actually work accurately as long as you handle the
|
||||
* values correctly */
|
||||
|
||||
static inline double ts_to_frames(const audio_t *audio, uint64_t ts)
|
||||
{
|
||||
double audio_offset_d = (double)ts;
|
||||
audio_offset_d /= 1000000000.0;
|
||||
audio_offset_d *= (double)audio->info.samples_per_sec;
|
||||
|
||||
return audio_offset_d;
|
||||
}
|
||||
|
||||
static inline double positive_round(double val)
|
||||
{
|
||||
return floor(val+0.5);
|
||||
}
|
||||
|
||||
static int64_t ts_diff_frames(const audio_t *audio, uint64_t ts1, uint64_t ts2)
|
||||
{
|
||||
double diff = ts_to_frames(audio, ts1) - ts_to_frames(audio, ts2);
|
||||
return (int64_t)positive_round(diff);
|
||||
}
|
||||
|
||||
static int64_t ts_diff_bytes(const audio_t *audio, uint64_t ts1, uint64_t ts2)
|
||||
{
|
||||
return ts_diff_frames(audio, ts1, ts2) * (int64_t)audio->block_size;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static inline uint64_t min_uint64(uint64_t a, uint64_t b)
|
||||
{
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
static inline size_t min_size(size_t a, size_t b)
|
||||
{
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
#ifndef CLAMP
|
||||
#define CLAMP(val, minval, maxval) \
|
||||
((val > maxval) ? maxval : ((val < minval) ? minval : val))
|
||||
#endif
|
||||
|
||||
static bool resample_audio_output(struct audio_input *input,
|
||||
struct audio_data *data)
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
if (input->resampler) {
|
||||
uint8_t *output[MAX_AV_PLANES];
|
||||
uint32_t frames;
|
||||
uint64_t offset;
|
||||
|
||||
memset(output, 0, sizeof(output));
|
||||
|
||||
success = audio_resampler_resample(input->resampler,
|
||||
output, &frames, &offset,
|
||||
(const uint8_t *const *)data->data,
|
||||
data->frames);
|
||||
|
||||
for (size_t i = 0; i < MAX_AV_PLANES; i++)
|
||||
data->data[i] = output[i];
|
||||
data->frames = frames;
|
||||
data->timestamp -= offset;
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static inline void do_audio_output(struct audio_output *audio,
|
||||
size_t mix_idx, uint64_t timestamp, uint32_t frames)
|
||||
{
|
||||
struct audio_mix *mix = &audio->mixes[mix_idx];
|
||||
struct audio_data data;
|
||||
|
||||
pthread_mutex_lock(&audio->input_mutex);
|
||||
|
||||
for (size_t i = mix->inputs.num; i > 0; i--) {
|
||||
struct audio_input *input = mix->inputs.array+(i-1);
|
||||
|
||||
for (size_t i = 0; i < audio->planes; i++)
|
||||
data.data[i] = (uint8_t*)mix->buffer[i];
|
||||
data.frames = frames;
|
||||
data.timestamp = timestamp;
|
||||
|
||||
if (resample_audio_output(input, &data))
|
||||
input->callback(input->param, mix_idx, &data);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&audio->input_mutex);
|
||||
}
|
||||
|
||||
static inline void clamp_audio_output(struct audio_output *audio, size_t bytes)
|
||||
{
|
||||
size_t float_size = bytes / sizeof(float);
|
||||
|
||||
for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
|
||||
struct audio_mix *mix = &audio->mixes[mix_idx];
|
||||
|
||||
/* do not process mixing if a specific mix is inactive */
|
||||
if (!mix->inputs.num)
|
||||
continue;
|
||||
|
||||
for (size_t plane = 0; plane < audio->planes; plane++) {
|
||||
float *mix_data = mix->buffer[plane];
|
||||
float *mix_end = &mix_data[float_size];
|
||||
|
||||
while (mix_data < mix_end) {
|
||||
float val = *mix_data;
|
||||
val = (val > 1.0f) ? 1.0f : val;
|
||||
val = (val < -1.0f) ? -1.0f : val;
|
||||
*(mix_data++) = val;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static void input_and_output(struct audio_output *audio,
|
||||
uint64_t audio_time, uint64_t prev_time)
|
||||
{
|
||||
size_t bytes = AUDIO_OUTPUT_FRAMES * audio->block_size;
|
||||
struct audio_output_data data[MAX_AUDIO_MIXES];
|
||||
uint32_t active_mixes = 0;
|
||||
uint64_t new_ts = 0;
|
||||
bool success;
|
||||
|
||||
memset(data, 0, sizeof(data));
|
||||
|
||||
#ifdef DEBUG_AUDIO
|
||||
blog(LOG_DEBUG, "audio_time: %llu, prev_time: %llu, bytes: %lu",
|
||||
audio_time, prev_time, bytes);
|
||||
#endif
|
||||
|
||||
/* get mixers */
|
||||
pthread_mutex_lock(&audio->input_mutex);
|
||||
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++) {
|
||||
if (audio->mixes[i].inputs.num)
|
||||
active_mixes |= (1 << i);
|
||||
}
|
||||
pthread_mutex_unlock(&audio->input_mutex);
|
||||
|
||||
/* clear mix buffers */
|
||||
for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
|
||||
struct audio_mix *mix = &audio->mixes[mix_idx];
|
||||
|
||||
memset(mix->buffer[0], 0, AUDIO_OUTPUT_FRAMES *
|
||||
MAX_AUDIO_CHANNELS * sizeof(float));
|
||||
|
||||
for (size_t i = 0; i < audio->planes; i++)
|
||||
data[mix_idx].data[i] = mix->buffer[i];
|
||||
}
|
||||
|
||||
/* get new audio data */
|
||||
success = audio->input_cb(audio->input_param, prev_time, audio_time,
|
||||
&new_ts, active_mixes, data);
|
||||
if (!success)
|
||||
return;
|
||||
|
||||
/* clamps audio data to -1.0..1.0 */
|
||||
clamp_audio_output(audio, bytes);
|
||||
|
||||
/* output */
|
||||
for (size_t i = 0; i < MAX_AUDIO_MIXES; i++)
|
||||
do_audio_output(audio, i, new_ts, AUDIO_OUTPUT_FRAMES);
|
||||
}
|
||||
|
||||
static void *audio_thread(void *param)
|
||||
{
|
||||
struct audio_output *audio = param;
|
||||
size_t rate = audio->info.samples_per_sec;
|
||||
uint64_t samples = 0;
|
||||
uint64_t start_time = os_gettime_ns();
|
||||
uint64_t prev_time = start_time;
|
||||
uint64_t audio_time = prev_time;
|
||||
uint32_t audio_wait_time =
|
||||
(uint32_t)(audio_frames_to_ns(rate, AUDIO_OUTPUT_FRAMES) /
|
||||
1000000);
|
||||
|
||||
os_set_thread_name("audio-io: audio thread");
|
||||
|
||||
const char *audio_thread_name =
|
||||
profile_store_name(obs_get_profiler_name_store(),
|
||||
"audio_thread(%s)", audio->info.name);
|
||||
|
||||
while (os_event_try(audio->stop_event) == EAGAIN) {
|
||||
uint64_t cur_time;
|
||||
|
||||
os_sleep_ms(audio_wait_time);
|
||||
|
||||
profile_start(audio_thread_name);
|
||||
|
||||
cur_time = os_gettime_ns();
|
||||
while (audio_time <= cur_time) {
|
||||
samples += AUDIO_OUTPUT_FRAMES;
|
||||
audio_time = start_time +
|
||||
audio_frames_to_ns(rate, samples);
|
||||
|
||||
input_and_output(audio, audio_time, prev_time);
|
||||
prev_time = audio_time;
|
||||
}
|
||||
|
||||
profile_end(audio_thread_name);
|
||||
|
||||
profile_reenable_thread();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static size_t audio_get_input_idx(const audio_t *audio, size_t mix_idx,
|
||||
audio_output_callback_t callback, void *param)
|
||||
{
|
||||
const struct audio_mix *mix = &audio->mixes[mix_idx];
|
||||
|
||||
for (size_t i = 0; i < mix->inputs.num; i++) {
|
||||
struct audio_input *input = mix->inputs.array+i;
|
||||
|
||||
if (input->callback == callback && input->param == param)
|
||||
return i;
|
||||
}
|
||||
|
||||
return DARRAY_INVALID;
|
||||
}
|
||||
|
||||
static inline bool audio_input_init(struct audio_input *input,
|
||||
struct audio_output *audio)
|
||||
{
|
||||
if (input->conversion.format != audio->info.format ||
|
||||
input->conversion.samples_per_sec != audio->info.samples_per_sec ||
|
||||
input->conversion.speakers != audio->info.speakers) {
|
||||
struct resample_info from = {
|
||||
.format = audio->info.format,
|
||||
.samples_per_sec = audio->info.samples_per_sec,
|
||||
.speakers = audio->info.speakers
|
||||
};
|
||||
|
||||
struct resample_info to = {
|
||||
.format = input->conversion.format,
|
||||
.samples_per_sec = input->conversion.samples_per_sec,
|
||||
.speakers = input->conversion.speakers
|
||||
};
|
||||
|
||||
input->resampler = audio_resampler_create(&to, &from);
|
||||
if (!input->resampler) {
|
||||
blog(LOG_ERROR, "audio_input_init: Failed to "
|
||||
"create resampler");
|
||||
return false;
|
||||
}
|
||||
} else {
|
||||
input->resampler = NULL;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool audio_output_connect(audio_t *audio, size_t mi,
|
||||
const struct audio_convert_info *conversion,
|
||||
audio_output_callback_t callback, void *param)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
if (!audio || mi >= MAX_AUDIO_MIXES) return false;
|
||||
|
||||
pthread_mutex_lock(&audio->input_mutex);
|
||||
|
||||
if (audio_get_input_idx(audio, mi, callback, param) == DARRAY_INVALID) {
|
||||
struct audio_mix *mix = &audio->mixes[mi];
|
||||
struct audio_input input;
|
||||
input.callback = callback;
|
||||
input.param = param;
|
||||
|
||||
if (conversion) {
|
||||
input.conversion = *conversion;
|
||||
} else {
|
||||
input.conversion.format = audio->info.format;
|
||||
input.conversion.speakers = audio->info.speakers;
|
||||
input.conversion.samples_per_sec =
|
||||
audio->info.samples_per_sec;
|
||||
}
|
||||
|
||||
if (input.conversion.format == AUDIO_FORMAT_UNKNOWN)
|
||||
input.conversion.format = audio->info.format;
|
||||
if (input.conversion.speakers == SPEAKERS_UNKNOWN)
|
||||
input.conversion.speakers = audio->info.speakers;
|
||||
if (input.conversion.samples_per_sec == 0)
|
||||
input.conversion.samples_per_sec =
|
||||
audio->info.samples_per_sec;
|
||||
|
||||
success = audio_input_init(&input, audio);
|
||||
if (success)
|
||||
da_push_back(mix->inputs, &input);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&audio->input_mutex);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void audio_output_disconnect(audio_t *audio, size_t mix_idx,
|
||||
audio_output_callback_t callback, void *param)
|
||||
{
|
||||
if (!audio || mix_idx >= MAX_AUDIO_MIXES) return;
|
||||
|
||||
pthread_mutex_lock(&audio->input_mutex);
|
||||
|
||||
size_t idx = audio_get_input_idx(audio, mix_idx, callback, param);
|
||||
if (idx != DARRAY_INVALID) {
|
||||
struct audio_mix *mix = &audio->mixes[mix_idx];
|
||||
audio_input_free(mix->inputs.array+idx);
|
||||
da_erase(mix->inputs, idx);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&audio->input_mutex);
|
||||
}
|
||||
|
||||
static inline bool valid_audio_params(const struct audio_output_info *info)
|
||||
{
|
||||
return info->format && info->name && info->samples_per_sec > 0 &&
|
||||
info->speakers > 0;
|
||||
}
|
||||
|
||||
int audio_output_open(audio_t **audio, struct audio_output_info *info)
|
||||
{
|
||||
struct audio_output *out;
|
||||
pthread_mutexattr_t attr;
|
||||
bool planar = is_audio_planar(info->format);
|
||||
|
||||
if (!valid_audio_params(info))
|
||||
return AUDIO_OUTPUT_INVALIDPARAM;
|
||||
|
||||
out = bzalloc(sizeof(struct audio_output));
|
||||
if (!out)
|
||||
goto fail;
|
||||
|
||||
memcpy(&out->info, info, sizeof(struct audio_output_info));
|
||||
out->channels = get_audio_channels(info->speakers);
|
||||
out->planes = planar ? out->channels : 1;
|
||||
out->input_cb = info->input_callback;
|
||||
out->input_param= info->input_param;
|
||||
out->block_size = (planar ? 1 : out->channels) *
|
||||
get_audio_bytes_per_channel(info->format);
|
||||
|
||||
if (pthread_mutexattr_init(&attr) != 0)
|
||||
goto fail;
|
||||
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
|
||||
goto fail;
|
||||
if (pthread_mutex_init(&out->input_mutex, &attr) != 0)
|
||||
goto fail;
|
||||
if (os_event_init(&out->stop_event, OS_EVENT_TYPE_MANUAL) != 0)
|
||||
goto fail;
|
||||
if (pthread_create(&out->thread, NULL, audio_thread, out) != 0)
|
||||
goto fail;
|
||||
|
||||
out->initialized = true;
|
||||
*audio = out;
|
||||
return AUDIO_OUTPUT_SUCCESS;
|
||||
|
||||
fail:
|
||||
audio_output_close(out);
|
||||
return AUDIO_OUTPUT_FAIL;
|
||||
}
|
||||
|
||||
void audio_output_close(audio_t *audio)
|
||||
{
|
||||
void *thread_ret;
|
||||
|
||||
if (!audio)
|
||||
return;
|
||||
|
||||
if (audio->initialized) {
|
||||
os_event_signal(audio->stop_event);
|
||||
pthread_join(audio->thread, &thread_ret);
|
||||
}
|
||||
|
||||
for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
|
||||
struct audio_mix *mix = &audio->mixes[mix_idx];
|
||||
|
||||
for (size_t i = 0; i < mix->inputs.num; i++)
|
||||
audio_input_free(mix->inputs.array+i);
|
||||
|
||||
da_free(mix->inputs);
|
||||
}
|
||||
|
||||
os_event_destroy(audio->stop_event);
|
||||
bfree(audio);
|
||||
}
|
||||
|
||||
const struct audio_output_info *audio_output_get_info(const audio_t *audio)
|
||||
{
|
||||
return audio ? &audio->info : NULL;
|
||||
}
|
||||
|
||||
bool audio_output_active(const audio_t *audio)
|
||||
{
|
||||
if (!audio) return false;
|
||||
|
||||
for (size_t mix_idx = 0; mix_idx < MAX_AUDIO_MIXES; mix_idx++) {
|
||||
const struct audio_mix *mix = &audio->mixes[mix_idx];
|
||||
|
||||
if (mix->inputs.num != 0)
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
size_t audio_output_get_block_size(const audio_t *audio)
|
||||
{
|
||||
return audio ? audio->block_size : 0;
|
||||
}
|
||||
|
||||
size_t audio_output_get_planes(const audio_t *audio)
|
||||
{
|
||||
return audio ? audio->planes : 0;
|
||||
}
|
||||
|
||||
size_t audio_output_get_channels(const audio_t *audio)
|
||||
{
|
||||
return audio ? audio->channels : 0;
|
||||
}
|
||||
|
||||
uint32_t audio_output_get_sample_rate(const audio_t *audio)
|
||||
{
|
||||
return audio ? audio->info.samples_per_sec : 0;
|
||||
}
|
||||
226
libobs/media-io/audio-io.h
Normal file
226
libobs/media-io/audio-io.h
Normal file
|
|
@ -0,0 +1,226 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2013 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
|
||||
|
||||
#include "media-io-defs.h"
|
||||
#include "../util/c99defs.h"
|
||||
#include "../util/util_uint128.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#define MAX_AUDIO_MIXES 4
|
||||
#define MAX_AUDIO_CHANNELS 2
|
||||
#define AUDIO_OUTPUT_FRAMES 1024
|
||||
|
||||
/*
|
||||
* Base audio output component. Use this to create an audio output track
|
||||
* for the media.
|
||||
*/
|
||||
|
||||
struct audio_output;
|
||||
typedef struct audio_output audio_t;
|
||||
|
||||
enum audio_format {
|
||||
AUDIO_FORMAT_UNKNOWN,
|
||||
|
||||
AUDIO_FORMAT_U8BIT,
|
||||
AUDIO_FORMAT_16BIT,
|
||||
AUDIO_FORMAT_32BIT,
|
||||
AUDIO_FORMAT_FLOAT,
|
||||
|
||||
AUDIO_FORMAT_U8BIT_PLANAR,
|
||||
AUDIO_FORMAT_16BIT_PLANAR,
|
||||
AUDIO_FORMAT_32BIT_PLANAR,
|
||||
AUDIO_FORMAT_FLOAT_PLANAR,
|
||||
};
|
||||
|
||||
enum speaker_layout {
|
||||
SPEAKERS_UNKNOWN,
|
||||
SPEAKERS_MONO,
|
||||
SPEAKERS_STEREO,
|
||||
SPEAKERS_2POINT1,
|
||||
SPEAKERS_QUAD,
|
||||
SPEAKERS_4POINT1,
|
||||
SPEAKERS_5POINT1,
|
||||
SPEAKERS_5POINT1_SURROUND,
|
||||
SPEAKERS_7POINT1,
|
||||
SPEAKERS_7POINT1_SURROUND,
|
||||
SPEAKERS_SURROUND,
|
||||
};
|
||||
|
||||
struct audio_data {
|
||||
uint8_t *data[MAX_AV_PLANES];
|
||||
uint32_t frames;
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
struct audio_output_data {
|
||||
float *data[MAX_AUDIO_CHANNELS];
|
||||
};
|
||||
|
||||
typedef bool (*audio_input_callback_t)(void *param,
|
||||
uint64_t start_ts, uint64_t end_ts, uint64_t *new_ts,
|
||||
uint32_t active_mixers, struct audio_output_data *mixes);
|
||||
|
||||
struct audio_output_info {
|
||||
const char *name;
|
||||
|
||||
uint32_t samples_per_sec;
|
||||
enum audio_format format;
|
||||
enum speaker_layout speakers;
|
||||
|
||||
audio_input_callback_t input_callback;
|
||||
void *input_param;
|
||||
};
|
||||
|
||||
struct audio_convert_info {
|
||||
uint32_t samples_per_sec;
|
||||
enum audio_format format;
|
||||
enum speaker_layout speakers;
|
||||
};
|
||||
|
||||
static inline uint32_t get_audio_channels(enum speaker_layout speakers)
|
||||
{
|
||||
switch (speakers) {
|
||||
case SPEAKERS_MONO: return 1;
|
||||
case SPEAKERS_STEREO: return 2;
|
||||
case SPEAKERS_2POINT1: return 3;
|
||||
case SPEAKERS_SURROUND:
|
||||
case SPEAKERS_QUAD: return 4;
|
||||
case SPEAKERS_4POINT1: return 5;
|
||||
case SPEAKERS_5POINT1:
|
||||
case SPEAKERS_5POINT1_SURROUND: return 6;
|
||||
case SPEAKERS_7POINT1: return 8;
|
||||
case SPEAKERS_7POINT1_SURROUND: return 8;
|
||||
case SPEAKERS_UNKNOWN: return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline size_t get_audio_bytes_per_channel(enum audio_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case AUDIO_FORMAT_U8BIT:
|
||||
case AUDIO_FORMAT_U8BIT_PLANAR:
|
||||
return 1;
|
||||
|
||||
case AUDIO_FORMAT_16BIT:
|
||||
case AUDIO_FORMAT_16BIT_PLANAR:
|
||||
return 2;
|
||||
|
||||
case AUDIO_FORMAT_FLOAT:
|
||||
case AUDIO_FORMAT_FLOAT_PLANAR:
|
||||
case AUDIO_FORMAT_32BIT:
|
||||
case AUDIO_FORMAT_32BIT_PLANAR:
|
||||
return 4;
|
||||
|
||||
case AUDIO_FORMAT_UNKNOWN:
|
||||
return 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
static inline bool is_audio_planar(enum audio_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case AUDIO_FORMAT_U8BIT:
|
||||
case AUDIO_FORMAT_16BIT:
|
||||
case AUDIO_FORMAT_32BIT:
|
||||
case AUDIO_FORMAT_FLOAT:
|
||||
return false;
|
||||
|
||||
case AUDIO_FORMAT_U8BIT_PLANAR:
|
||||
case AUDIO_FORMAT_FLOAT_PLANAR:
|
||||
case AUDIO_FORMAT_16BIT_PLANAR:
|
||||
case AUDIO_FORMAT_32BIT_PLANAR:
|
||||
return true;
|
||||
|
||||
case AUDIO_FORMAT_UNKNOWN:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline size_t get_audio_planes(enum audio_format format,
|
||||
enum speaker_layout speakers)
|
||||
{
|
||||
return (is_audio_planar(format) ? get_audio_channels(speakers) : 1);
|
||||
}
|
||||
|
||||
static inline size_t get_audio_size(enum audio_format format,
|
||||
enum speaker_layout speakers, uint32_t frames)
|
||||
{
|
||||
bool planar = is_audio_planar(format);
|
||||
|
||||
return (planar ? 1 : get_audio_channels(speakers)) *
|
||||
get_audio_bytes_per_channel(format) *
|
||||
frames;
|
||||
}
|
||||
|
||||
static inline uint64_t audio_frames_to_ns(size_t sample_rate,
|
||||
uint64_t frames)
|
||||
{
|
||||
util_uint128_t val;
|
||||
val = util_mul64_64(frames, 1000000000ULL);
|
||||
val = util_div128_32(val, (uint32_t)sample_rate);
|
||||
return val.low;
|
||||
}
|
||||
|
||||
static inline uint64_t ns_to_audio_frames(size_t sample_rate,
|
||||
uint64_t frames)
|
||||
{
|
||||
util_uint128_t val;
|
||||
val = util_mul64_64(frames, sample_rate);
|
||||
val = util_div128_32(val, 1000000000);
|
||||
return val.low;
|
||||
}
|
||||
|
||||
#define AUDIO_OUTPUT_SUCCESS 0
|
||||
#define AUDIO_OUTPUT_INVALIDPARAM -1
|
||||
#define AUDIO_OUTPUT_FAIL -2
|
||||
|
||||
EXPORT int audio_output_open(audio_t **audio, struct audio_output_info *info);
|
||||
EXPORT void audio_output_close(audio_t *audio);
|
||||
|
||||
typedef void (*audio_output_callback_t)(void *param, size_t mix_idx,
|
||||
struct audio_data *data);
|
||||
|
||||
EXPORT bool audio_output_connect(audio_t *video, size_t mix_idx,
|
||||
const struct audio_convert_info *conversion,
|
||||
audio_output_callback_t callback, void *param);
|
||||
EXPORT void audio_output_disconnect(audio_t *video, size_t mix_idx,
|
||||
audio_output_callback_t callback, void *param);
|
||||
|
||||
EXPORT bool audio_output_active(const audio_t *audio);
|
||||
|
||||
EXPORT size_t audio_output_get_block_size(const audio_t *audio);
|
||||
EXPORT size_t audio_output_get_planes(const audio_t *audio);
|
||||
EXPORT size_t audio_output_get_channels(const audio_t *audio);
|
||||
EXPORT uint32_t audio_output_get_sample_rate(const audio_t *audio);
|
||||
EXPORT const struct audio_output_info *audio_output_get_info(
|
||||
const audio_t *audio);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
43
libobs/media-io/audio-math.h
Normal file
43
libobs/media-io/audio-math.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2015 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
|
||||
|
||||
#include "../util/c99defs.h"
|
||||
#include <math.h>
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#include <float.h>
|
||||
|
||||
#pragma warning(push)
|
||||
#pragma warning(disable : 4056)
|
||||
#pragma warning(disable : 4756)
|
||||
#endif
|
||||
|
||||
static inline float mul_to_db(const float mul)
|
||||
{
|
||||
return (mul == 0.0f) ? -INFINITY : (20.0f * log10f(mul));
|
||||
}
|
||||
|
||||
static inline float db_to_mul(const float db)
|
||||
{
|
||||
return isfinite((double)db) ? powf(10.0f, db / 20.0f) : 0.0f;
|
||||
}
|
||||
|
||||
#ifdef _MSC_VER
|
||||
#pragma warning(pop)
|
||||
#endif
|
||||
173
libobs/media-io/audio-resampler-ffmpeg.c
Normal file
173
libobs/media-io/audio-resampler-ffmpeg.c
Normal file
|
|
@ -0,0 +1,173 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2013 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 "../util/bmem.h"
|
||||
#include "audio-resampler.h"
|
||||
#include "audio-io.h"
|
||||
#include <libavutil/avutil.h>
|
||||
#include <libavformat/avformat.h>
|
||||
#include <libswresample/swresample.h>
|
||||
|
||||
struct audio_resampler {
|
||||
struct SwrContext *context;
|
||||
bool opened;
|
||||
|
||||
uint32_t input_freq;
|
||||
uint64_t input_layout;
|
||||
enum AVSampleFormat input_format;
|
||||
|
||||
uint8_t *output_buffer[MAX_AV_PLANES];
|
||||
uint64_t output_layout;
|
||||
enum AVSampleFormat output_format;
|
||||
int output_size;
|
||||
uint32_t output_ch;
|
||||
uint32_t output_freq;
|
||||
uint32_t output_planes;
|
||||
};
|
||||
|
||||
static inline enum AVSampleFormat convert_audio_format(enum audio_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case AUDIO_FORMAT_UNKNOWN: return AV_SAMPLE_FMT_S16;
|
||||
case AUDIO_FORMAT_U8BIT: return AV_SAMPLE_FMT_U8;
|
||||
case AUDIO_FORMAT_16BIT: return AV_SAMPLE_FMT_S16;
|
||||
case AUDIO_FORMAT_32BIT: return AV_SAMPLE_FMT_S32;
|
||||
case AUDIO_FORMAT_FLOAT: return AV_SAMPLE_FMT_FLT;
|
||||
case AUDIO_FORMAT_U8BIT_PLANAR: return AV_SAMPLE_FMT_U8P;
|
||||
case AUDIO_FORMAT_16BIT_PLANAR: return AV_SAMPLE_FMT_S16P;
|
||||
case AUDIO_FORMAT_32BIT_PLANAR: return AV_SAMPLE_FMT_S32P;
|
||||
case AUDIO_FORMAT_FLOAT_PLANAR: return AV_SAMPLE_FMT_FLTP;
|
||||
}
|
||||
|
||||
/* shouldn't get here */
|
||||
return AV_SAMPLE_FMT_S16;
|
||||
}
|
||||
|
||||
static inline uint64_t convert_speaker_layout(enum speaker_layout layout)
|
||||
{
|
||||
switch (layout) {
|
||||
case SPEAKERS_UNKNOWN: return 0;
|
||||
case SPEAKERS_MONO: return AV_CH_LAYOUT_MONO;
|
||||
case SPEAKERS_STEREO: return AV_CH_LAYOUT_STEREO;
|
||||
case SPEAKERS_2POINT1: return AV_CH_LAYOUT_2_1;
|
||||
case SPEAKERS_QUAD: return AV_CH_LAYOUT_QUAD;
|
||||
case SPEAKERS_4POINT1: return AV_CH_LAYOUT_4POINT1;
|
||||
case SPEAKERS_5POINT1: return AV_CH_LAYOUT_5POINT1;
|
||||
case SPEAKERS_5POINT1_SURROUND: return AV_CH_LAYOUT_5POINT1_BACK;
|
||||
case SPEAKERS_7POINT1: return AV_CH_LAYOUT_7POINT1;
|
||||
case SPEAKERS_7POINT1_SURROUND: return AV_CH_LAYOUT_7POINT1_WIDE_BACK;
|
||||
case SPEAKERS_SURROUND: return AV_CH_LAYOUT_SURROUND;
|
||||
}
|
||||
|
||||
/* shouldn't get here */
|
||||
return 0;
|
||||
}
|
||||
|
||||
audio_resampler_t *audio_resampler_create(const struct resample_info *dst,
|
||||
const struct resample_info *src)
|
||||
{
|
||||
struct audio_resampler *rs = bzalloc(sizeof(struct audio_resampler));
|
||||
int errcode;
|
||||
|
||||
rs->opened = false;
|
||||
rs->input_freq = src->samples_per_sec;
|
||||
rs->input_layout = convert_speaker_layout(src->speakers);
|
||||
rs->input_format = convert_audio_format(src->format);
|
||||
rs->output_size = 0;
|
||||
rs->output_ch = get_audio_channels(dst->speakers);
|
||||
rs->output_freq = dst->samples_per_sec;
|
||||
rs->output_layout = convert_speaker_layout(dst->speakers);
|
||||
rs->output_format = convert_audio_format(dst->format);
|
||||
rs->output_planes = is_audio_planar(dst->format) ? rs->output_ch : 1;
|
||||
|
||||
rs->context = swr_alloc_set_opts(NULL,
|
||||
rs->output_layout, rs->output_format, dst->samples_per_sec,
|
||||
rs->input_layout, rs->input_format, src->samples_per_sec,
|
||||
0, NULL);
|
||||
|
||||
if (!rs->context) {
|
||||
blog(LOG_ERROR, "swr_alloc_set_opts failed");
|
||||
audio_resampler_destroy(rs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
errcode = swr_init(rs->context);
|
||||
if (errcode != 0) {
|
||||
blog(LOG_ERROR, "avresample_open failed: error code %d",
|
||||
errcode);
|
||||
audio_resampler_destroy(rs);
|
||||
return NULL;
|
||||
}
|
||||
|
||||
return rs;
|
||||
}
|
||||
|
||||
void audio_resampler_destroy(audio_resampler_t *rs)
|
||||
{
|
||||
if (rs) {
|
||||
if (rs->context)
|
||||
swr_free(&rs->context);
|
||||
if (rs->output_buffer[0])
|
||||
av_freep(&rs->output_buffer[0]);
|
||||
|
||||
bfree(rs);
|
||||
}
|
||||
}
|
||||
|
||||
bool audio_resampler_resample(audio_resampler_t *rs,
|
||||
uint8_t *output[], uint32_t *out_frames, uint64_t *ts_offset,
|
||||
const uint8_t *const input[], uint32_t in_frames)
|
||||
{
|
||||
if (!rs) return false;
|
||||
|
||||
struct SwrContext *context = rs->context;
|
||||
int ret;
|
||||
|
||||
int64_t delay = swr_get_delay(context, rs->input_freq);
|
||||
int estimated = (int)av_rescale_rnd(
|
||||
delay + (int64_t)in_frames,
|
||||
(int64_t)rs->output_freq, (int64_t)rs->input_freq,
|
||||
AV_ROUND_UP);
|
||||
|
||||
*ts_offset = (uint64_t)swr_get_delay(context, 1000000000);
|
||||
|
||||
/* resize the buffer if bigger */
|
||||
if (estimated > rs->output_size) {
|
||||
if (rs->output_buffer[0])
|
||||
av_freep(&rs->output_buffer[0]);
|
||||
|
||||
av_samples_alloc(rs->output_buffer, NULL, rs->output_ch,
|
||||
estimated, rs->output_format, 0);
|
||||
|
||||
rs->output_size = estimated;
|
||||
}
|
||||
|
||||
ret = swr_convert(context,
|
||||
rs->output_buffer, rs->output_size,
|
||||
(const uint8_t**)input, in_frames);
|
||||
|
||||
if (ret < 0) {
|
||||
blog(LOG_ERROR, "swr_convert failed: %d", ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < rs->output_planes; i++)
|
||||
output[i] = rs->output_buffer[i];
|
||||
|
||||
*out_frames = (uint32_t)ret;
|
||||
return true;
|
||||
}
|
||||
46
libobs/media-io/audio-resampler.h
Normal file
46
libobs/media-io/audio-resampler.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2013 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
|
||||
|
||||
#include "../util/c99defs.h"
|
||||
#include "audio-io.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct audio_resampler;
|
||||
typedef struct audio_resampler audio_resampler_t;
|
||||
|
||||
struct resample_info {
|
||||
uint32_t samples_per_sec;
|
||||
enum audio_format format;
|
||||
enum speaker_layout speakers;
|
||||
};
|
||||
|
||||
EXPORT audio_resampler_t *audio_resampler_create(const struct resample_info *dst,
|
||||
const struct resample_info *src);
|
||||
EXPORT void audio_resampler_destroy(audio_resampler_t *resampler);
|
||||
|
||||
EXPORT bool audio_resampler_resample(audio_resampler_t *resampler,
|
||||
uint8_t *output[], uint32_t *out_frames, uint64_t *ts_offset,
|
||||
const uint8_t *const input[], uint32_t in_frames);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
324
libobs/media-io/format-conversion.c
Normal file
324
libobs/media-io/format-conversion.c
Normal file
|
|
@ -0,0 +1,324 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2013 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 "format-conversion.h"
|
||||
#include <xmmintrin.h>
|
||||
#include <emmintrin.h>
|
||||
|
||||
/* ...surprisingly, if I don't use a macro to force inlining, it causes the
|
||||
* CPU usage to boost by a tremendous amount in debug builds. */
|
||||
|
||||
#define get_m128_32_0(val) (*((uint32_t*)&val))
|
||||
#define get_m128_32_1(val) (*(((uint32_t*)&val)+1))
|
||||
|
||||
#define pack_shift(lum_plane, lum_pos0, lum_pos1, line1, line2, mask, sh) \
|
||||
do { \
|
||||
__m128i pack_val = _mm_packs_epi32( \
|
||||
_mm_srli_si128(_mm_and_si128(line1, mask), sh), \
|
||||
_mm_srli_si128(_mm_and_si128(line2, mask), sh)); \
|
||||
pack_val = _mm_packus_epi16(pack_val, pack_val); \
|
||||
\
|
||||
*(uint32_t*)(lum_plane+lum_pos0) = get_m128_32_0(pack_val); \
|
||||
*(uint32_t*)(lum_plane+lum_pos1) = get_m128_32_1(pack_val); \
|
||||
} while (false)
|
||||
|
||||
#define pack_val(lum_plane, lum_pos0, lum_pos1, line1, line2, mask) \
|
||||
do { \
|
||||
__m128i pack_val = _mm_packs_epi32( \
|
||||
_mm_and_si128(line1, mask), \
|
||||
_mm_and_si128(line2, mask)); \
|
||||
pack_val = _mm_packus_epi16(pack_val, pack_val); \
|
||||
\
|
||||
*(uint32_t*)(lum_plane+lum_pos0) = get_m128_32_0(pack_val); \
|
||||
*(uint32_t*)(lum_plane+lum_pos1) = get_m128_32_1(pack_val); \
|
||||
} while (false)
|
||||
|
||||
#define pack_ch_1plane(uv_plane, chroma_pos, line1, line2, uv_mask) \
|
||||
do { \
|
||||
__m128i add_val = _mm_add_epi64( \
|
||||
_mm_and_si128(line1, uv_mask), \
|
||||
_mm_and_si128(line2, uv_mask)); \
|
||||
__m128i avg_val = _mm_add_epi64( \
|
||||
add_val, \
|
||||
_mm_shuffle_epi32(add_val, _MM_SHUFFLE(2, 3, 0, 1))); \
|
||||
avg_val = _mm_srai_epi16(avg_val, 2); \
|
||||
avg_val = _mm_shuffle_epi32(avg_val, _MM_SHUFFLE(3, 1, 2, 0)); \
|
||||
avg_val = _mm_packus_epi16(avg_val, avg_val); \
|
||||
\
|
||||
*(uint32_t*)(uv_plane+chroma_pos) = get_m128_32_0(avg_val); \
|
||||
} while (false)
|
||||
|
||||
#define pack_ch_2plane(u_plane, v_plane, chroma_pos, line1, line2, uv_mask) \
|
||||
do { \
|
||||
uint32_t packed_vals; \
|
||||
\
|
||||
__m128i add_val = _mm_add_epi64( \
|
||||
_mm_and_si128(line1, uv_mask), \
|
||||
_mm_and_si128(line2, uv_mask)); \
|
||||
__m128i avg_val = _mm_add_epi64( \
|
||||
add_val, \
|
||||
_mm_shuffle_epi32(add_val, _MM_SHUFFLE(2, 3, 0, 1))); \
|
||||
avg_val = _mm_srai_epi16(avg_val, 2); \
|
||||
avg_val = _mm_shuffle_epi32(avg_val, _MM_SHUFFLE(3, 1, 2, 0)); \
|
||||
avg_val = _mm_shufflelo_epi16(avg_val, _MM_SHUFFLE(3, 1, 2, 0)); \
|
||||
avg_val = _mm_packus_epi16(avg_val, avg_val); \
|
||||
\
|
||||
packed_vals = get_m128_32_0(avg_val); \
|
||||
\
|
||||
*(uint16_t*)(u_plane+chroma_pos) = (uint16_t)(packed_vals); \
|
||||
*(uint16_t*)(v_plane+chroma_pos) = (uint16_t)(packed_vals>>16); \
|
||||
} while (false)
|
||||
|
||||
|
||||
static FORCE_INLINE uint32_t min_uint32(uint32_t a, uint32_t b)
|
||||
{
|
||||
return a < b ? a : b;
|
||||
}
|
||||
|
||||
void compress_uyvx_to_i420(
|
||||
const uint8_t *input, uint32_t in_linesize,
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output[], const uint32_t out_linesize[])
|
||||
{
|
||||
uint8_t *lum_plane = output[0];
|
||||
uint8_t *u_plane = output[1];
|
||||
uint8_t *v_plane = output[2];
|
||||
uint32_t width = min_uint32(in_linesize, out_linesize[0]);
|
||||
uint32_t y;
|
||||
|
||||
__m128i lum_mask = _mm_set1_epi32(0x0000FF00);
|
||||
__m128i uv_mask = _mm_set1_epi16(0x00FF);
|
||||
|
||||
for (y = start_y; y < end_y; y += 2) {
|
||||
uint32_t y_pos = y * in_linesize;
|
||||
uint32_t chroma_y_pos = (y>>1) * out_linesize[1];
|
||||
uint32_t lum_y_pos = y * out_linesize[0];
|
||||
uint32_t x;
|
||||
|
||||
for (x = 0; x < width; x += 4) {
|
||||
const uint8_t *img = input + y_pos + x*4;
|
||||
uint32_t lum_pos0 = lum_y_pos + x;
|
||||
uint32_t lum_pos1 = lum_pos0 + out_linesize[0];
|
||||
|
||||
__m128i line1 = _mm_load_si128((const __m128i*)img);
|
||||
__m128i line2 = _mm_load_si128(
|
||||
(const __m128i*)(img + in_linesize));
|
||||
|
||||
pack_shift(lum_plane, lum_pos0, lum_pos1,
|
||||
line1, line2, lum_mask, 1);
|
||||
pack_ch_2plane(u_plane, v_plane,
|
||||
chroma_y_pos + (x>>1),
|
||||
line1, line2, uv_mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void compress_uyvx_to_nv12(
|
||||
const uint8_t *input, uint32_t in_linesize,
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output[], const uint32_t out_linesize[])
|
||||
{
|
||||
uint8_t *lum_plane = output[0];
|
||||
uint8_t *chroma_plane = output[1];
|
||||
uint32_t width = min_uint32(in_linesize, out_linesize[0]);
|
||||
uint32_t y;
|
||||
|
||||
__m128i lum_mask = _mm_set1_epi32(0x0000FF00);
|
||||
__m128i uv_mask = _mm_set1_epi16(0x00FF);
|
||||
|
||||
for (y = start_y; y < end_y; y += 2) {
|
||||
uint32_t y_pos = y * in_linesize;
|
||||
uint32_t chroma_y_pos = (y>>1) * out_linesize[1];
|
||||
uint32_t lum_y_pos = y * out_linesize[0];
|
||||
uint32_t x;
|
||||
|
||||
for (x = 0; x < width; x += 4) {
|
||||
const uint8_t *img = input + y_pos + x*4;
|
||||
uint32_t lum_pos0 = lum_y_pos + x;
|
||||
uint32_t lum_pos1 = lum_pos0 + out_linesize[0];
|
||||
|
||||
__m128i line1 = _mm_load_si128((const __m128i*)img);
|
||||
__m128i line2 = _mm_load_si128(
|
||||
(const __m128i*)(img + in_linesize));
|
||||
|
||||
pack_shift(lum_plane, lum_pos0, lum_pos1,
|
||||
line1, line2, lum_mask, 1);
|
||||
pack_ch_1plane(chroma_plane, chroma_y_pos + x,
|
||||
line1, line2, uv_mask);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void convert_uyvx_to_i444(
|
||||
const uint8_t *input, uint32_t in_linesize,
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output[], const uint32_t out_linesize[])
|
||||
{
|
||||
uint8_t *lum_plane = output[0];
|
||||
uint8_t *u_plane = output[1];
|
||||
uint8_t *v_plane = output[2];
|
||||
uint32_t width = min_uint32(in_linesize, out_linesize[0]);
|
||||
uint32_t y;
|
||||
|
||||
__m128i lum_mask = _mm_set1_epi32(0x0000FF00);
|
||||
__m128i u_mask = _mm_set1_epi32(0x000000FF);
|
||||
__m128i v_mask = _mm_set1_epi32(0x00FF0000);
|
||||
|
||||
for (y = start_y; y < end_y; y += 2) {
|
||||
uint32_t y_pos = y * in_linesize;
|
||||
uint32_t lum_y_pos = y * out_linesize[0];
|
||||
uint32_t x;
|
||||
|
||||
for (x = 0; x < width; x += 4) {
|
||||
const uint8_t *img = input + y_pos + x*4;
|
||||
uint32_t lum_pos0 = lum_y_pos + x;
|
||||
uint32_t lum_pos1 = lum_pos0 + out_linesize[0];
|
||||
|
||||
__m128i line1 = _mm_load_si128((const __m128i*)img);
|
||||
__m128i line2 = _mm_load_si128(
|
||||
(const __m128i*)(img + in_linesize));
|
||||
|
||||
pack_shift(lum_plane, lum_pos0, lum_pos1,
|
||||
line1, line2, lum_mask, 1);
|
||||
pack_val(u_plane, lum_pos0, lum_pos1,
|
||||
line1, line2, u_mask);
|
||||
pack_shift(v_plane, lum_pos0, lum_pos1,
|
||||
line1, line2, v_mask, 2);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decompress_420(
|
||||
const uint8_t *const input[], const uint32_t in_linesize[],
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output, uint32_t out_linesize)
|
||||
{
|
||||
uint32_t start_y_d2 = start_y/2;
|
||||
uint32_t width_d2 = min_uint32(in_linesize[0], out_linesize)/2;
|
||||
uint32_t height_d2 = end_y/2;
|
||||
uint32_t y;
|
||||
|
||||
for (y = start_y_d2; y < height_d2; y++) {
|
||||
const uint8_t *chroma0 = input[1] + y * in_linesize[1];
|
||||
const uint8_t *chroma1 = input[2] + y * in_linesize[2];
|
||||
register const uint8_t *lum0, *lum1;
|
||||
register uint32_t *output0, *output1;
|
||||
uint32_t x;
|
||||
|
||||
lum0 = input[0] + y * 2 * in_linesize[0];
|
||||
lum1 = lum0 + in_linesize[0];
|
||||
output0 = (uint32_t*)(output + y * 2 * in_linesize[0]);
|
||||
output1 = (uint32_t*)((uint8_t*)output0 + in_linesize[0]);
|
||||
|
||||
for (x = 0; x < width_d2; x++) {
|
||||
uint32_t out;
|
||||
out = (*(chroma0++) << 8) | (*(chroma1++) << 16);
|
||||
|
||||
*(output0++) = *(lum0++) | out;
|
||||
*(output0++) = *(lum0++) | out;
|
||||
|
||||
*(output1++) = *(lum1++) | out;
|
||||
*(output1++) = *(lum1++) | out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decompress_nv12(
|
||||
const uint8_t *const input[], const uint32_t in_linesize[],
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output, uint32_t out_linesize)
|
||||
{
|
||||
uint32_t start_y_d2 = start_y/2;
|
||||
uint32_t width_d2 = min_uint32(in_linesize[0], out_linesize)/2;
|
||||
uint32_t height_d2 = end_y/2;
|
||||
uint32_t y;
|
||||
|
||||
for (y = start_y_d2; y < height_d2; y++) {
|
||||
const uint16_t *chroma;
|
||||
register const uint8_t *lum0, *lum1;
|
||||
register uint32_t *output0, *output1;
|
||||
uint32_t x;
|
||||
|
||||
chroma = (const uint16_t*)(input[1] + y * in_linesize[1]);
|
||||
lum0 = input[0] + y * 2 * in_linesize[0];
|
||||
lum1 = lum0 + in_linesize[0];
|
||||
output0 = (uint32_t*)(output + y * 2 * out_linesize);
|
||||
output1 = (uint32_t*)((uint8_t*)output0 + out_linesize);
|
||||
|
||||
for (x = 0; x < width_d2; x++) {
|
||||
uint32_t out = *(chroma++) << 8;
|
||||
|
||||
*(output0++) = *(lum0++) | out;
|
||||
*(output0++) = *(lum0++) | out;
|
||||
|
||||
*(output1++) = *(lum1++) | out;
|
||||
*(output1++) = *(lum1++) | out;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void decompress_422(
|
||||
const uint8_t *input, uint32_t in_linesize,
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output, uint32_t out_linesize,
|
||||
bool leading_lum)
|
||||
{
|
||||
uint32_t width_d2 = min_uint32(in_linesize, out_linesize)/2;
|
||||
uint32_t y;
|
||||
|
||||
register const uint32_t *input32;
|
||||
register const uint32_t *input32_end;
|
||||
register uint32_t *output32;
|
||||
|
||||
if (leading_lum) {
|
||||
for (y = start_y; y < end_y; y++) {
|
||||
input32 = (const uint32_t*)(input + y*in_linesize);
|
||||
input32_end = input32 + width_d2;
|
||||
output32 = (uint32_t*)(output + y*out_linesize);
|
||||
|
||||
while(input32 < input32_end) {
|
||||
register uint32_t dw = *input32;
|
||||
|
||||
output32[0] = dw;
|
||||
dw &= 0xFFFFFF00;
|
||||
dw |= (uint8_t)(dw>>16);
|
||||
output32[1] = dw;
|
||||
|
||||
output32 += 2;
|
||||
input32++;
|
||||
}
|
||||
}
|
||||
} else {
|
||||
for (y = start_y; y < end_y; y++) {
|
||||
input32 = (const uint32_t*)(input + y*in_linesize);
|
||||
input32_end = input32 + width_d2;
|
||||
output32 = (uint32_t*)(output + y*out_linesize);
|
||||
|
||||
while (input32 < input32_end) {
|
||||
register uint32_t dw = *input32;
|
||||
|
||||
output32[0] = dw;
|
||||
dw &= 0xFFFF00FF;
|
||||
dw |= (dw>>16) & 0xFF00;
|
||||
output32[1] = dw;
|
||||
|
||||
output32 += 2;
|
||||
input32++;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
63
libobs/media-io/format-conversion.h
Normal file
63
libobs/media-io/format-conversion.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2013 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
|
||||
|
||||
#include "../util/c99defs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
/*
|
||||
* Functions for converting to and from packed 444 YUV
|
||||
*/
|
||||
|
||||
EXPORT void compress_uyvx_to_i420(
|
||||
const uint8_t *input, uint32_t in_linesize,
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output[], const uint32_t out_linesize[]);
|
||||
|
||||
EXPORT void compress_uyvx_to_nv12(
|
||||
const uint8_t *input, uint32_t in_linesize,
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output[], const uint32_t out_linesize[]);
|
||||
|
||||
EXPORT void convert_uyvx_to_i444(
|
||||
const uint8_t *input, uint32_t in_linesize,
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output[], const uint32_t out_linesize[]);
|
||||
|
||||
EXPORT void decompress_nv12(
|
||||
const uint8_t *const input[], const uint32_t in_linesize[],
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output, uint32_t out_linesize);
|
||||
|
||||
EXPORT void decompress_420(
|
||||
const uint8_t *const input[], const uint32_t in_linesize[],
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output, uint32_t out_linesize);
|
||||
|
||||
EXPORT void decompress_422(
|
||||
const uint8_t *input, uint32_t in_linesize,
|
||||
uint32_t start_y, uint32_t end_y,
|
||||
uint8_t *output, uint32_t out_linesize,
|
||||
bool leading_lum);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
32
libobs/media-io/frame-rate.h
Normal file
32
libobs/media-io/frame-rate.h
Normal file
|
|
@ -0,0 +1,32 @@
|
|||
#pragma once
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct media_frames_per_second {
|
||||
uint32_t numerator;
|
||||
uint32_t denominator;
|
||||
};
|
||||
|
||||
static inline double media_frames_per_second_to_frame_interval(
|
||||
struct media_frames_per_second fps)
|
||||
{
|
||||
return (double)fps.denominator / fps.numerator;
|
||||
}
|
||||
|
||||
static inline double media_frames_per_second_to_fps(
|
||||
struct media_frames_per_second fps)
|
||||
{
|
||||
return (double)fps.numerator / fps.denominator;
|
||||
}
|
||||
|
||||
static inline bool media_frames_per_second_is_valid(
|
||||
struct media_frames_per_second fps)
|
||||
{
|
||||
return fps.numerator && fps.denominator;
|
||||
}
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
24
libobs/media-io/media-io-defs.h
Normal file
24
libobs/media-io/media-io-defs.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
/******************************************************************************
|
||||
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
|
||||
|
||||
#define MAX_AV_PLANES 8
|
||||
|
||||
/* time threshold in nanoseconds to ensure audio timing is as seamless as
|
||||
* possible */
|
||||
#define TS_SMOOTHING_THRESHOLD 70000000ULL
|
||||
251
libobs/media-io/media-remux.c
Normal file
251
libobs/media-io/media-remux.c
Normal file
|
|
@ -0,0 +1,251 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2014 by Ruwen Hahn <palana@stunned.de>
|
||||
|
||||
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 "media-remux.h"
|
||||
|
||||
#include "../util/base.h"
|
||||
#include "../util/bmem.h"
|
||||
#include "../util/platform.h"
|
||||
|
||||
#include <libavformat/avformat.h>
|
||||
|
||||
#include <sys/types.h>
|
||||
#include <sys/stat.h>
|
||||
|
||||
struct media_remux_job {
|
||||
int64_t in_size;
|
||||
AVFormatContext *ifmt_ctx, *ofmt_ctx;
|
||||
};
|
||||
|
||||
static inline void init_size(media_remux_job_t job, const char *in_filename)
|
||||
{
|
||||
#ifdef _MSC_VER
|
||||
struct _stat64 st = {0};
|
||||
_stat64(in_filename, &st);
|
||||
#else
|
||||
struct stat st = {0};
|
||||
stat(in_filename, &st);
|
||||
#endif
|
||||
job->in_size = st.st_size;
|
||||
}
|
||||
|
||||
static inline bool init_input(media_remux_job_t job, const char *in_filename)
|
||||
{
|
||||
int ret = avformat_open_input(&job->ifmt_ctx, in_filename, NULL, NULL);
|
||||
if (ret < 0) {
|
||||
blog(LOG_ERROR, "media_remux: Could not open input file '%s'",
|
||||
in_filename);
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = avformat_find_stream_info(job->ifmt_ctx, NULL);
|
||||
if (ret < 0) {
|
||||
blog(LOG_ERROR, "media_remux: Failed to retrieve input stream"
|
||||
" information");
|
||||
return false;
|
||||
}
|
||||
|
||||
#ifndef _NDEBUG
|
||||
av_dump_format(job->ifmt_ctx, 0, in_filename, false);
|
||||
#endif
|
||||
return true;
|
||||
}
|
||||
|
||||
static inline bool init_output(media_remux_job_t job, const char *out_filename)
|
||||
{
|
||||
int ret;
|
||||
|
||||
avformat_alloc_output_context2(&job->ofmt_ctx, NULL, NULL,
|
||||
out_filename);
|
||||
if (!job->ofmt_ctx) {
|
||||
blog(LOG_ERROR, "media_remux: Could not create output context");
|
||||
return false;
|
||||
}
|
||||
|
||||
for (unsigned i = 0; i < job->ifmt_ctx->nb_streams; i++) {
|
||||
AVStream *in_stream = job->ifmt_ctx->streams[i];
|
||||
AVStream *out_stream = avformat_new_stream(job->ofmt_ctx,
|
||||
in_stream->codec->codec);
|
||||
if (!out_stream) {
|
||||
blog(LOG_ERROR, "media_remux: Failed to allocate output"
|
||||
" stream");
|
||||
return false;
|
||||
}
|
||||
|
||||
ret = avcodec_copy_context(out_stream->codec, in_stream->codec);
|
||||
if (ret < 0) {
|
||||
blog(LOG_ERROR, "media_remux: Failed to copy context");
|
||||
return false;
|
||||
}
|
||||
out_stream->time_base = out_stream->codec->time_base;
|
||||
|
||||
out_stream->codec->codec_tag = 0;
|
||||
if (job->ofmt_ctx->oformat->flags & AVFMT_GLOBALHEADER)
|
||||
out_stream->codec->flags |= CODEC_FLAG_GLOBAL_HEADER;
|
||||
}
|
||||
|
||||
#ifndef _NDEBUG
|
||||
av_dump_format(job->ofmt_ctx, 0, out_filename, true);
|
||||
#endif
|
||||
|
||||
if (!(job->ofmt_ctx->oformat->flags & AVFMT_NOFILE)) {
|
||||
ret = avio_open(&job->ofmt_ctx->pb, out_filename,
|
||||
AVIO_FLAG_WRITE);
|
||||
if (ret < 0) {
|
||||
blog(LOG_ERROR, "media_remux: Failed to open output"
|
||||
" file '%s'", out_filename);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool media_remux_job_create(media_remux_job_t *job, const char *in_filename,
|
||||
const char *out_filename)
|
||||
{
|
||||
if (!job)
|
||||
return false;
|
||||
|
||||
*job = NULL;
|
||||
if (!os_file_exists(in_filename))
|
||||
return false;
|
||||
|
||||
*job = (media_remux_job_t)bzalloc(sizeof(struct media_remux_job));
|
||||
if (!*job)
|
||||
return false;
|
||||
|
||||
init_size(*job, in_filename);
|
||||
|
||||
av_register_all();
|
||||
|
||||
if (!init_input(*job, in_filename))
|
||||
goto fail;
|
||||
|
||||
if (!init_output(*job, out_filename))
|
||||
goto fail;
|
||||
|
||||
return true;
|
||||
|
||||
fail:
|
||||
media_remux_job_destroy(*job);
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline void process_packet(AVPacket *pkt,
|
||||
AVStream *in_stream, AVStream *out_stream)
|
||||
{
|
||||
pkt->pts = av_rescale_q_rnd(pkt->pts, in_stream->time_base,
|
||||
out_stream->time_base,
|
||||
AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
|
||||
pkt->dts = av_rescale_q_rnd(pkt->dts, in_stream->time_base,
|
||||
out_stream->time_base,
|
||||
AV_ROUND_NEAR_INF|AV_ROUND_PASS_MINMAX);
|
||||
pkt->duration = (int)av_rescale_q(pkt->duration,
|
||||
in_stream->time_base, out_stream->time_base);
|
||||
pkt->pos = -1;
|
||||
|
||||
}
|
||||
|
||||
static inline int process_packets(media_remux_job_t job,
|
||||
media_remux_progress_callback callback, void *data)
|
||||
{
|
||||
AVPacket pkt;
|
||||
|
||||
int ret, throttle = 0;
|
||||
for (;;) {
|
||||
ret = av_read_frame(job->ifmt_ctx, &pkt);
|
||||
if (ret < 0) {
|
||||
if (ret != AVERROR_EOF)
|
||||
blog(LOG_ERROR, "media_remux: Error reading"
|
||||
" packet: %s",
|
||||
av_err2str(ret));
|
||||
break;
|
||||
}
|
||||
|
||||
if (callback != NULL && throttle++ > 10) {
|
||||
float progress = pkt.pos / (float)job->in_size * 100.f;
|
||||
if (!callback(data, progress))
|
||||
break;
|
||||
throttle = 0;
|
||||
}
|
||||
|
||||
process_packet(&pkt, job->ifmt_ctx->streams[pkt.stream_index],
|
||||
job->ofmt_ctx->streams[pkt.stream_index]);
|
||||
|
||||
ret = av_interleaved_write_frame(job->ofmt_ctx, &pkt);
|
||||
av_free_packet(&pkt);
|
||||
|
||||
if (ret < 0) {
|
||||
blog(LOG_ERROR, "media_remux: Error muxing packet: %s",
|
||||
av_err2str(ret));
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
bool media_remux_job_process(media_remux_job_t job,
|
||||
media_remux_progress_callback callback, void *data)
|
||||
{
|
||||
int ret;
|
||||
bool success = false;
|
||||
|
||||
if (!job)
|
||||
return success;
|
||||
|
||||
ret = avformat_write_header(job->ofmt_ctx, NULL);
|
||||
if (ret < 0) {
|
||||
blog(LOG_ERROR, "media_remux: Error opening output file: %s",
|
||||
av_err2str(ret));
|
||||
return success;
|
||||
}
|
||||
|
||||
if (callback != NULL)
|
||||
callback(data, 0.f);
|
||||
|
||||
ret = process_packets(job, callback, data);
|
||||
success = ret >= 0 || ret == AVERROR_EOF;
|
||||
|
||||
ret = av_write_trailer(job->ofmt_ctx);
|
||||
if (ret < 0) {
|
||||
blog(LOG_ERROR, "media_remux: av_write_trailer: %s",
|
||||
av_err2str(ret));
|
||||
success = false;
|
||||
}
|
||||
|
||||
if (callback != NULL)
|
||||
callback(data, 100.f);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void media_remux_job_destroy(media_remux_job_t job)
|
||||
{
|
||||
if (!job)
|
||||
return;
|
||||
|
||||
avformat_close_input(&job->ifmt_ctx);
|
||||
|
||||
if (job->ofmt_ctx && !(job->ofmt_ctx->oformat->flags & AVFMT_NOFILE))
|
||||
avio_close(job->ofmt_ctx->pb);
|
||||
|
||||
avformat_free_context(job->ofmt_ctx);
|
||||
|
||||
bfree(job);
|
||||
}
|
||||
39
libobs/media-io/media-remux.h
Normal file
39
libobs/media-io/media-remux.h
Normal file
|
|
@ -0,0 +1,39 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2014 by Ruwen Hahn <palana@stunned.de>
|
||||
|
||||
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 "../util/c99defs.h"
|
||||
|
||||
#pragma once
|
||||
|
||||
struct media_remux_job;
|
||||
typedef struct media_remux_job *media_remux_job_t;
|
||||
|
||||
typedef bool (media_remux_progress_callback)(void *data, float percent);
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
EXPORT bool media_remux_job_create(media_remux_job_t *job,
|
||||
const char *in_filename, const char *out_filename);
|
||||
EXPORT bool media_remux_job_process(media_remux_job_t job,
|
||||
media_remux_progress_callback callback, void *data);
|
||||
EXPORT void media_remux_job_destroy(media_remux_job_t job);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
51
libobs/media-io/video-fourcc.c
Normal file
51
libobs/media-io/video-fourcc.c
Normal file
|
|
@ -0,0 +1,51 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2014 by Ruwen Hahn <palana@stunned.de>
|
||||
|
||||
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 "../util/c99defs.h"
|
||||
#include "video-io.h"
|
||||
|
||||
#define MAKE_FOURCC(a, b, c, d) \
|
||||
((uint32_t)(((d) << 24) | ((c) << 16) | ((b) << 8) | (a)))
|
||||
|
||||
enum video_format video_format_from_fourcc(uint32_t fourcc)
|
||||
{
|
||||
switch (fourcc) {
|
||||
case MAKE_FOURCC('U','Y','V','Y'):
|
||||
case MAKE_FOURCC('H','D','Y','C'):
|
||||
case MAKE_FOURCC('U','Y','N','V'):
|
||||
case MAKE_FOURCC('U','Y','N','Y'):
|
||||
case MAKE_FOURCC('u','y','v','1'):
|
||||
case MAKE_FOURCC('2','v','u','y'):
|
||||
case MAKE_FOURCC('2','V','u','y'):
|
||||
return VIDEO_FORMAT_UYVY;
|
||||
|
||||
case MAKE_FOURCC('Y','U','Y','2'):
|
||||
case MAKE_FOURCC('Y','4','2','2'):
|
||||
case MAKE_FOURCC('V','4','2','2'):
|
||||
case MAKE_FOURCC('V','Y','U','Y'):
|
||||
case MAKE_FOURCC('Y','U','N','V'):
|
||||
case MAKE_FOURCC('y','u','v','2'):
|
||||
case MAKE_FOURCC('y','u','v','s'):
|
||||
return VIDEO_FORMAT_YUY2;
|
||||
|
||||
case MAKE_FOURCC('Y','V','Y','U'):
|
||||
return VIDEO_FORMAT_YVYU;
|
||||
|
||||
}
|
||||
return VIDEO_FORMAT_NONE;
|
||||
}
|
||||
|
||||
133
libobs/media-io/video-frame.c
Normal file
133
libobs/media-io/video-frame.c
Normal file
|
|
@ -0,0 +1,133 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2013 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 "video-frame.h"
|
||||
|
||||
#define ALIGN_SIZE(size, align) \
|
||||
size = (((size)+(align-1)) & (~(align-1)))
|
||||
|
||||
/* messy code alarm */
|
||||
void video_frame_init(struct video_frame *frame, enum video_format format,
|
||||
uint32_t width, uint32_t height)
|
||||
{
|
||||
size_t size;
|
||||
size_t offsets[MAX_AV_PLANES];
|
||||
int alignment = base_get_alignment();
|
||||
|
||||
if (!frame) return;
|
||||
|
||||
memset(frame, 0, sizeof(struct video_frame));
|
||||
memset(offsets, 0, sizeof(offsets));
|
||||
|
||||
switch (format) {
|
||||
case VIDEO_FORMAT_NONE:
|
||||
return;
|
||||
|
||||
case VIDEO_FORMAT_I420:
|
||||
size = width * height;
|
||||
ALIGN_SIZE(size, alignment);
|
||||
offsets[0] = size;
|
||||
size += (width/2) * (height/2);
|
||||
ALIGN_SIZE(size, alignment);
|
||||
offsets[1] = size;
|
||||
size += (width/2) * (height/2);
|
||||
ALIGN_SIZE(size, alignment);
|
||||
frame->data[0] = bmalloc(size);
|
||||
frame->data[1] = (uint8_t*)frame->data[0] + offsets[0];
|
||||
frame->data[2] = (uint8_t*)frame->data[0] + offsets[1];
|
||||
frame->linesize[0] = width;
|
||||
frame->linesize[1] = width/2;
|
||||
frame->linesize[2] = width/2;
|
||||
break;
|
||||
|
||||
case VIDEO_FORMAT_NV12:
|
||||
size = width * height;
|
||||
ALIGN_SIZE(size, alignment);
|
||||
offsets[0] = size;
|
||||
size += (width/2) * (height/2) * 2;
|
||||
ALIGN_SIZE(size, alignment);
|
||||
frame->data[0] = bmalloc(size);
|
||||
frame->data[1] = (uint8_t*)frame->data[0] + offsets[0];
|
||||
frame->linesize[0] = width;
|
||||
frame->linesize[1] = width;
|
||||
break;
|
||||
|
||||
case VIDEO_FORMAT_YVYU:
|
||||
case VIDEO_FORMAT_YUY2:
|
||||
case VIDEO_FORMAT_UYVY:
|
||||
size = width * height * 2;
|
||||
ALIGN_SIZE(size, alignment);
|
||||
frame->data[0] = bmalloc(size);
|
||||
frame->linesize[0] = width*2;
|
||||
break;
|
||||
|
||||
case VIDEO_FORMAT_RGBA:
|
||||
case VIDEO_FORMAT_BGRA:
|
||||
case VIDEO_FORMAT_BGRX:
|
||||
size = width * height * 4;
|
||||
ALIGN_SIZE(size, alignment);
|
||||
frame->data[0] = bmalloc(size);
|
||||
frame->linesize[0] = width*4;
|
||||
break;
|
||||
|
||||
case VIDEO_FORMAT_I444:
|
||||
size = width * height;
|
||||
ALIGN_SIZE(size, alignment);
|
||||
frame->data[0] = bmalloc(size * 3);
|
||||
frame->data[1] = (uint8_t*)frame->data[0] + size;
|
||||
frame->data[2] = (uint8_t*)frame->data[1] + size;
|
||||
frame->linesize[0] = width;
|
||||
frame->linesize[1] = width;
|
||||
frame->linesize[2] = width;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void video_frame_copy(struct video_frame *dst, const struct video_frame *src,
|
||||
enum video_format format, uint32_t cy)
|
||||
{
|
||||
switch (format) {
|
||||
case VIDEO_FORMAT_NONE:
|
||||
return;
|
||||
|
||||
case VIDEO_FORMAT_I420:
|
||||
memcpy(dst->data[0], src->data[0], src->linesize[0] * cy);
|
||||
memcpy(dst->data[1], src->data[1], src->linesize[1] * cy / 2);
|
||||
memcpy(dst->data[2], src->data[2], src->linesize[2] * cy / 2);
|
||||
break;
|
||||
|
||||
case VIDEO_FORMAT_NV12:
|
||||
memcpy(dst->data[0], src->data[0], src->linesize[0] * cy);
|
||||
memcpy(dst->data[1], src->data[1], src->linesize[1] * cy / 2);
|
||||
break;
|
||||
|
||||
case VIDEO_FORMAT_YVYU:
|
||||
case VIDEO_FORMAT_YUY2:
|
||||
case VIDEO_FORMAT_UYVY:
|
||||
case VIDEO_FORMAT_RGBA:
|
||||
case VIDEO_FORMAT_BGRA:
|
||||
case VIDEO_FORMAT_BGRX:
|
||||
memcpy(dst->data[0], src->data[0], src->linesize[0] * cy);
|
||||
break;
|
||||
|
||||
case VIDEO_FORMAT_I444:
|
||||
memcpy(dst->data[0], src->data[0], src->linesize[0] * cy);
|
||||
memcpy(dst->data[1], src->data[1], src->linesize[1] * cy);
|
||||
memcpy(dst->data[2], src->data[2], src->linesize[2] * cy);
|
||||
break;
|
||||
}
|
||||
}
|
||||
59
libobs/media-io/video-frame.h
Normal file
59
libobs/media-io/video-frame.h
Normal file
|
|
@ -0,0 +1,59 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2013 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
|
||||
|
||||
#include "../util/bmem.h"
|
||||
#include "video-io.h"
|
||||
|
||||
struct video_frame {
|
||||
uint8_t *data[MAX_AV_PLANES];
|
||||
uint32_t linesize[MAX_AV_PLANES];
|
||||
};
|
||||
|
||||
EXPORT void video_frame_init(struct video_frame *frame,
|
||||
enum video_format format, uint32_t width, uint32_t height);
|
||||
|
||||
static inline void video_frame_free(struct video_frame *frame)
|
||||
{
|
||||
if (frame) {
|
||||
bfree(frame->data[0]);
|
||||
memset(frame, 0, sizeof(struct video_frame));
|
||||
}
|
||||
}
|
||||
|
||||
static inline struct video_frame *video_frame_create(
|
||||
enum video_format format, uint32_t width, uint32_t height)
|
||||
{
|
||||
struct video_frame *frame;
|
||||
|
||||
frame = (struct video_frame*)bzalloc(sizeof(struct video_frame));
|
||||
video_frame_init(frame, format, width, height);
|
||||
return frame;
|
||||
}
|
||||
|
||||
static inline void video_frame_destroy(struct video_frame *frame)
|
||||
{
|
||||
if (frame) {
|
||||
bfree(frame->data[0]);
|
||||
bfree(frame);
|
||||
}
|
||||
}
|
||||
|
||||
EXPORT void video_frame_copy(struct video_frame *dst,
|
||||
const struct video_frame *src, enum video_format format,
|
||||
uint32_t height);
|
||||
501
libobs/media-io/video-io.c
Normal file
501
libobs/media-io/video-io.c
Normal file
|
|
@ -0,0 +1,501 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2013 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 <assert.h>
|
||||
#include "../util/bmem.h"
|
||||
#include "../util/platform.h"
|
||||
#include "../util/profiler.h"
|
||||
#include "../util/threading.h"
|
||||
#include "../util/darray.h"
|
||||
|
||||
#include "format-conversion.h"
|
||||
#include "video-io.h"
|
||||
#include "video-frame.h"
|
||||
#include "video-scaler.h"
|
||||
|
||||
extern profiler_name_store_t *obs_get_profiler_name_store(void);
|
||||
|
||||
#define MAX_CONVERT_BUFFERS 3
|
||||
#define MAX_CACHE_SIZE 16
|
||||
|
||||
struct cached_frame_info {
|
||||
struct video_data frame;
|
||||
int count;
|
||||
};
|
||||
|
||||
struct video_input {
|
||||
struct video_scale_info conversion;
|
||||
video_scaler_t *scaler;
|
||||
struct video_frame frame[MAX_CONVERT_BUFFERS];
|
||||
int cur_frame;
|
||||
|
||||
void (*callback)(void *param, struct video_data *frame);
|
||||
void *param;
|
||||
};
|
||||
|
||||
static inline void video_input_free(struct video_input *input)
|
||||
{
|
||||
for (size_t i = 0; i < MAX_CONVERT_BUFFERS; i++)
|
||||
video_frame_free(&input->frame[i]);
|
||||
video_scaler_destroy(input->scaler);
|
||||
}
|
||||
|
||||
struct video_output {
|
||||
struct video_output_info info;
|
||||
|
||||
pthread_t thread;
|
||||
pthread_mutex_t data_mutex;
|
||||
bool stop;
|
||||
|
||||
os_sem_t *update_semaphore;
|
||||
uint64_t frame_time;
|
||||
uint32_t skipped_frames;
|
||||
uint32_t total_frames;
|
||||
|
||||
bool initialized;
|
||||
|
||||
pthread_mutex_t input_mutex;
|
||||
DARRAY(struct video_input) inputs;
|
||||
|
||||
size_t available_frames;
|
||||
size_t first_added;
|
||||
size_t last_added;
|
||||
struct cached_frame_info cache[MAX_CACHE_SIZE];
|
||||
};
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static inline bool scale_video_output(struct video_input *input,
|
||||
struct video_data *data)
|
||||
{
|
||||
bool success = true;
|
||||
|
||||
if (input->scaler) {
|
||||
struct video_frame *frame;
|
||||
|
||||
if (++input->cur_frame == MAX_CONVERT_BUFFERS)
|
||||
input->cur_frame = 0;
|
||||
|
||||
frame = &input->frame[input->cur_frame];
|
||||
|
||||
success = video_scaler_scale(input->scaler,
|
||||
frame->data, frame->linesize,
|
||||
(const uint8_t * const*)data->data,
|
||||
data->linesize);
|
||||
|
||||
if (success) {
|
||||
for (size_t i = 0; i < MAX_AV_PLANES; i++) {
|
||||
data->data[i] = frame->data[i];
|
||||
data->linesize[i] = frame->linesize[i];
|
||||
}
|
||||
} else {
|
||||
blog(LOG_WARNING, "video-io: Could not scale frame!");
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
static inline bool video_output_cur_frame(struct video_output *video)
|
||||
{
|
||||
struct cached_frame_info *frame_info;
|
||||
bool complete;
|
||||
|
||||
/* -------------------------------- */
|
||||
|
||||
pthread_mutex_lock(&video->data_mutex);
|
||||
|
||||
frame_info = &video->cache[video->first_added];
|
||||
|
||||
pthread_mutex_unlock(&video->data_mutex);
|
||||
|
||||
/* -------------------------------- */
|
||||
|
||||
pthread_mutex_lock(&video->input_mutex);
|
||||
|
||||
for (size_t i = 0; i < video->inputs.num; i++) {
|
||||
struct video_input *input = video->inputs.array+i;
|
||||
struct video_data frame = frame_info->frame;
|
||||
|
||||
if (scale_video_output(input, &frame))
|
||||
input->callback(input->param, &frame);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&video->input_mutex);
|
||||
|
||||
/* -------------------------------- */
|
||||
|
||||
pthread_mutex_lock(&video->data_mutex);
|
||||
|
||||
frame_info->frame.timestamp += video->frame_time;
|
||||
complete = --frame_info->count == 0;
|
||||
|
||||
if (complete) {
|
||||
if (++video->first_added == video->info.cache_size)
|
||||
video->first_added = 0;
|
||||
|
||||
if (++video->available_frames == video->info.cache_size)
|
||||
video->last_added = video->first_added;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&video->data_mutex);
|
||||
|
||||
/* -------------------------------- */
|
||||
|
||||
return complete;
|
||||
}
|
||||
|
||||
static void *video_thread(void *param)
|
||||
{
|
||||
struct video_output *video = param;
|
||||
|
||||
os_set_thread_name("video-io: video thread");
|
||||
|
||||
const char *video_thread_name =
|
||||
profile_store_name(obs_get_profiler_name_store(),
|
||||
"video_thread(%s)", video->info.name);
|
||||
|
||||
while (os_sem_wait(video->update_semaphore) == 0) {
|
||||
if (video->stop)
|
||||
break;
|
||||
|
||||
profile_start(video_thread_name);
|
||||
while (!video->stop && !video_output_cur_frame(video)) {
|
||||
video->total_frames++;
|
||||
}
|
||||
|
||||
video->total_frames++;
|
||||
profile_end(video_thread_name);
|
||||
|
||||
profile_reenable_thread();
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* ------------------------------------------------------------------------- */
|
||||
|
||||
static inline bool valid_video_params(const struct video_output_info *info)
|
||||
{
|
||||
return info->height != 0 && info->width != 0 && info->fps_den != 0 &&
|
||||
info->fps_num != 0;
|
||||
}
|
||||
|
||||
static inline void init_cache(struct video_output *video)
|
||||
{
|
||||
if (video->info.cache_size > MAX_CACHE_SIZE)
|
||||
video->info.cache_size = MAX_CACHE_SIZE;
|
||||
|
||||
for (size_t i = 0; i < video->info.cache_size; i++) {
|
||||
struct video_frame *frame;
|
||||
frame = (struct video_frame*)&video->cache[i];
|
||||
|
||||
video_frame_init(frame, video->info.format,
|
||||
video->info.width, video->info.height);
|
||||
}
|
||||
|
||||
video->available_frames = video->info.cache_size;
|
||||
}
|
||||
|
||||
int video_output_open(video_t **video, struct video_output_info *info)
|
||||
{
|
||||
struct video_output *out;
|
||||
pthread_mutexattr_t attr;
|
||||
|
||||
if (!valid_video_params(info))
|
||||
return VIDEO_OUTPUT_INVALIDPARAM;
|
||||
|
||||
out = bzalloc(sizeof(struct video_output));
|
||||
if (!out)
|
||||
goto fail;
|
||||
|
||||
memcpy(&out->info, info, sizeof(struct video_output_info));
|
||||
out->frame_time = (uint64_t)(1000000000.0 * (double)info->fps_den /
|
||||
(double)info->fps_num);
|
||||
out->initialized = false;
|
||||
|
||||
if (pthread_mutexattr_init(&attr) != 0)
|
||||
goto fail;
|
||||
if (pthread_mutexattr_settype(&attr, PTHREAD_MUTEX_RECURSIVE) != 0)
|
||||
goto fail;
|
||||
if (pthread_mutex_init(&out->data_mutex, &attr) != 0)
|
||||
goto fail;
|
||||
if (pthread_mutex_init(&out->input_mutex, &attr) != 0)
|
||||
goto fail;
|
||||
if (os_sem_init(&out->update_semaphore, 0) != 0)
|
||||
goto fail;
|
||||
if (pthread_create(&out->thread, NULL, video_thread, out) != 0)
|
||||
goto fail;
|
||||
|
||||
init_cache(out);
|
||||
|
||||
out->initialized = true;
|
||||
*video = out;
|
||||
return VIDEO_OUTPUT_SUCCESS;
|
||||
|
||||
fail:
|
||||
video_output_close(out);
|
||||
return VIDEO_OUTPUT_FAIL;
|
||||
}
|
||||
|
||||
void video_output_close(video_t *video)
|
||||
{
|
||||
if (!video)
|
||||
return;
|
||||
|
||||
video_output_stop(video);
|
||||
|
||||
for (size_t i = 0; i < video->inputs.num; i++)
|
||||
video_input_free(&video->inputs.array[i]);
|
||||
da_free(video->inputs);
|
||||
|
||||
for (size_t i = 0; i < video->info.cache_size; i++)
|
||||
video_frame_free((struct video_frame*)&video->cache[i]);
|
||||
|
||||
os_sem_destroy(video->update_semaphore);
|
||||
pthread_mutex_destroy(&video->data_mutex);
|
||||
pthread_mutex_destroy(&video->input_mutex);
|
||||
bfree(video);
|
||||
}
|
||||
|
||||
static size_t video_get_input_idx(const video_t *video,
|
||||
void (*callback)(void *param, struct video_data *frame),
|
||||
void *param)
|
||||
{
|
||||
for (size_t i = 0; i < video->inputs.num; i++) {
|
||||
struct video_input *input = video->inputs.array+i;
|
||||
if (input->callback == callback && input->param == param)
|
||||
return i;
|
||||
}
|
||||
|
||||
return DARRAY_INVALID;
|
||||
}
|
||||
|
||||
static inline bool video_input_init(struct video_input *input,
|
||||
struct video_output *video)
|
||||
{
|
||||
if (input->conversion.width != video->info.width ||
|
||||
input->conversion.height != video->info.height ||
|
||||
input->conversion.format != video->info.format) {
|
||||
struct video_scale_info from = {
|
||||
.format = video->info.format,
|
||||
.width = video->info.width,
|
||||
.height = video->info.height,
|
||||
};
|
||||
|
||||
int ret = video_scaler_create(&input->scaler,
|
||||
&input->conversion, &from,
|
||||
VIDEO_SCALE_FAST_BILINEAR);
|
||||
if (ret != VIDEO_SCALER_SUCCESS) {
|
||||
if (ret == VIDEO_SCALER_BAD_CONVERSION)
|
||||
blog(LOG_ERROR, "video_input_init: Bad "
|
||||
"scale conversion type");
|
||||
else
|
||||
blog(LOG_ERROR, "video_input_init: Failed to "
|
||||
"create scaler");
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
for (size_t i = 0; i < MAX_CONVERT_BUFFERS; i++)
|
||||
video_frame_init(&input->frame[i],
|
||||
input->conversion.format,
|
||||
input->conversion.width,
|
||||
input->conversion.height);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool video_output_connect(video_t *video,
|
||||
const struct video_scale_info *conversion,
|
||||
void (*callback)(void *param, struct video_data *frame),
|
||||
void *param)
|
||||
{
|
||||
bool success = false;
|
||||
|
||||
if (!video || !callback)
|
||||
return false;
|
||||
|
||||
pthread_mutex_lock(&video->input_mutex);
|
||||
|
||||
if (video_get_input_idx(video, callback, param) == DARRAY_INVALID) {
|
||||
struct video_input input;
|
||||
memset(&input, 0, sizeof(input));
|
||||
|
||||
input.callback = callback;
|
||||
input.param = param;
|
||||
|
||||
if (conversion) {
|
||||
input.conversion = *conversion;
|
||||
} else {
|
||||
input.conversion.format = video->info.format;
|
||||
input.conversion.width = video->info.width;
|
||||
input.conversion.height = video->info.height;
|
||||
}
|
||||
|
||||
if (input.conversion.width == 0)
|
||||
input.conversion.width = video->info.width;
|
||||
if (input.conversion.height == 0)
|
||||
input.conversion.height = video->info.height;
|
||||
|
||||
success = video_input_init(&input, video);
|
||||
if (success)
|
||||
da_push_back(video->inputs, &input);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&video->input_mutex);
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
void video_output_disconnect(video_t *video,
|
||||
void (*callback)(void *param, struct video_data *frame),
|
||||
void *param)
|
||||
{
|
||||
if (!video || !callback)
|
||||
return;
|
||||
|
||||
pthread_mutex_lock(&video->input_mutex);
|
||||
|
||||
size_t idx = video_get_input_idx(video, callback, param);
|
||||
if (idx != DARRAY_INVALID) {
|
||||
video_input_free(video->inputs.array+idx);
|
||||
da_erase(video->inputs, idx);
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&video->input_mutex);
|
||||
}
|
||||
|
||||
bool video_output_active(const video_t *video)
|
||||
{
|
||||
if (!video) return false;
|
||||
return video->inputs.num != 0;
|
||||
}
|
||||
|
||||
const struct video_output_info *video_output_get_info(const video_t *video)
|
||||
{
|
||||
return video ? &video->info : NULL;
|
||||
}
|
||||
|
||||
bool video_output_lock_frame(video_t *video, struct video_frame *frame,
|
||||
int count, uint64_t timestamp)
|
||||
{
|
||||
struct cached_frame_info *cfi;
|
||||
bool locked;
|
||||
|
||||
if (!video) return false;
|
||||
|
||||
pthread_mutex_lock(&video->data_mutex);
|
||||
|
||||
if (video->available_frames == 0) {
|
||||
video->skipped_frames += count;
|
||||
video->cache[video->last_added].count += count;
|
||||
locked = false;
|
||||
|
||||
} else {
|
||||
if (video->available_frames != video->info.cache_size) {
|
||||
if (++video->last_added == video->info.cache_size)
|
||||
video->last_added = 0;
|
||||
}
|
||||
|
||||
cfi = &video->cache[video->last_added];
|
||||
cfi->frame.timestamp = timestamp;
|
||||
cfi->count = count;
|
||||
|
||||
memcpy(frame, &cfi->frame, sizeof(*frame));
|
||||
|
||||
locked = true;
|
||||
}
|
||||
|
||||
pthread_mutex_unlock(&video->data_mutex);
|
||||
|
||||
return locked;
|
||||
}
|
||||
|
||||
void video_output_unlock_frame(video_t *video)
|
||||
{
|
||||
if (!video) return;
|
||||
|
||||
pthread_mutex_lock(&video->data_mutex);
|
||||
|
||||
video->available_frames--;
|
||||
os_sem_post(video->update_semaphore);
|
||||
|
||||
pthread_mutex_unlock(&video->data_mutex);
|
||||
}
|
||||
|
||||
uint64_t video_output_get_frame_time(const video_t *video)
|
||||
{
|
||||
return video ? video->frame_time : 0;
|
||||
}
|
||||
|
||||
void video_output_stop(video_t *video)
|
||||
{
|
||||
void *thread_ret;
|
||||
|
||||
if (!video)
|
||||
return;
|
||||
|
||||
if (video->initialized) {
|
||||
video->initialized = false;
|
||||
video->stop = true;
|
||||
os_sem_post(video->update_semaphore);
|
||||
pthread_join(video->thread, &thread_ret);
|
||||
}
|
||||
}
|
||||
|
||||
bool video_output_stopped(video_t *video)
|
||||
{
|
||||
if (!video)
|
||||
return true;
|
||||
|
||||
return video->stop;
|
||||
}
|
||||
|
||||
enum video_format video_output_get_format(const video_t *video)
|
||||
{
|
||||
return video ? video->info.format : VIDEO_FORMAT_NONE;
|
||||
}
|
||||
|
||||
uint32_t video_output_get_width(const video_t *video)
|
||||
{
|
||||
return video ? video->info.width : 0;
|
||||
}
|
||||
|
||||
uint32_t video_output_get_height(const video_t *video)
|
||||
{
|
||||
return video ? video->info.height : 0;
|
||||
}
|
||||
|
||||
double video_output_get_frame_rate(const video_t *video)
|
||||
{
|
||||
if (!video)
|
||||
return 0.0;
|
||||
|
||||
return (double)video->info.fps_num / (double)video->info.fps_den;
|
||||
}
|
||||
|
||||
uint32_t video_output_get_skipped_frames(const video_t *video)
|
||||
{
|
||||
return video->skipped_frames;
|
||||
}
|
||||
|
||||
uint32_t video_output_get_total_frames(const video_t *video)
|
||||
{
|
||||
return video->total_frames;
|
||||
}
|
||||
183
libobs/media-io/video-io.h
Normal file
183
libobs/media-io/video-io.h
Normal file
|
|
@ -0,0 +1,183 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2013 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
|
||||
|
||||
#include "media-io-defs.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct video_frame;
|
||||
|
||||
/* Base video output component. Use this to create a video output track. */
|
||||
|
||||
struct video_output;
|
||||
typedef struct video_output video_t;
|
||||
|
||||
enum video_format {
|
||||
VIDEO_FORMAT_NONE,
|
||||
|
||||
/* planar 420 format */
|
||||
VIDEO_FORMAT_I420, /* three-plane */
|
||||
VIDEO_FORMAT_NV12, /* two-plane, luma and packed chroma */
|
||||
|
||||
/* packed 422 formats */
|
||||
VIDEO_FORMAT_YVYU,
|
||||
VIDEO_FORMAT_YUY2, /* YUYV */
|
||||
VIDEO_FORMAT_UYVY,
|
||||
|
||||
/* packed uncompressed formats */
|
||||
VIDEO_FORMAT_RGBA,
|
||||
VIDEO_FORMAT_BGRA,
|
||||
VIDEO_FORMAT_BGRX,
|
||||
|
||||
/* planar 4:4:4 */
|
||||
VIDEO_FORMAT_I444,
|
||||
};
|
||||
|
||||
enum video_colorspace {
|
||||
VIDEO_CS_DEFAULT,
|
||||
VIDEO_CS_601,
|
||||
VIDEO_CS_709,
|
||||
};
|
||||
|
||||
enum video_range_type {
|
||||
VIDEO_RANGE_DEFAULT,
|
||||
VIDEO_RANGE_PARTIAL,
|
||||
VIDEO_RANGE_FULL
|
||||
};
|
||||
|
||||
struct video_data {
|
||||
uint8_t *data[MAX_AV_PLANES];
|
||||
uint32_t linesize[MAX_AV_PLANES];
|
||||
uint64_t timestamp;
|
||||
};
|
||||
|
||||
struct video_output_info {
|
||||
const char *name;
|
||||
|
||||
enum video_format format;
|
||||
uint32_t fps_num;
|
||||
uint32_t fps_den;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
size_t cache_size;
|
||||
|
||||
enum video_colorspace colorspace;
|
||||
enum video_range_type range;
|
||||
};
|
||||
|
||||
static inline bool format_is_yuv(enum video_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case VIDEO_FORMAT_I420:
|
||||
case VIDEO_FORMAT_NV12:
|
||||
case VIDEO_FORMAT_YVYU:
|
||||
case VIDEO_FORMAT_YUY2:
|
||||
case VIDEO_FORMAT_UYVY:
|
||||
case VIDEO_FORMAT_I444:
|
||||
return true;
|
||||
case VIDEO_FORMAT_NONE:
|
||||
case VIDEO_FORMAT_RGBA:
|
||||
case VIDEO_FORMAT_BGRA:
|
||||
case VIDEO_FORMAT_BGRX:
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
static inline const char *get_video_format_name(enum video_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case VIDEO_FORMAT_I420: return "I420";
|
||||
case VIDEO_FORMAT_NV12: return "NV12";
|
||||
case VIDEO_FORMAT_YVYU: return "YVYU";
|
||||
case VIDEO_FORMAT_YUY2: return "YUY2";
|
||||
case VIDEO_FORMAT_UYVY: return "UYVY";
|
||||
case VIDEO_FORMAT_RGBA: return "RGBA";
|
||||
case VIDEO_FORMAT_BGRA: return "BGRA";
|
||||
case VIDEO_FORMAT_BGRX: return "BGRX";
|
||||
case VIDEO_FORMAT_I444: return "I444";
|
||||
case VIDEO_FORMAT_NONE:;
|
||||
}
|
||||
|
||||
return "None";
|
||||
}
|
||||
|
||||
enum video_scale_type {
|
||||
VIDEO_SCALE_DEFAULT,
|
||||
VIDEO_SCALE_POINT,
|
||||
VIDEO_SCALE_FAST_BILINEAR,
|
||||
VIDEO_SCALE_BILINEAR,
|
||||
VIDEO_SCALE_BICUBIC,
|
||||
};
|
||||
|
||||
struct video_scale_info {
|
||||
enum video_format format;
|
||||
uint32_t width;
|
||||
uint32_t height;
|
||||
enum video_range_type range;
|
||||
enum video_colorspace colorspace;
|
||||
};
|
||||
|
||||
EXPORT enum video_format video_format_from_fourcc(uint32_t fourcc);
|
||||
|
||||
EXPORT bool video_format_get_parameters(enum video_colorspace color_space,
|
||||
enum video_range_type range, float matrix[16],
|
||||
float min_range[3], float max_range[3]);
|
||||
|
||||
#define VIDEO_OUTPUT_SUCCESS 0
|
||||
#define VIDEO_OUTPUT_INVALIDPARAM -1
|
||||
#define VIDEO_OUTPUT_FAIL -2
|
||||
|
||||
EXPORT int video_output_open(video_t **video, struct video_output_info *info);
|
||||
EXPORT void video_output_close(video_t *video);
|
||||
|
||||
EXPORT bool video_output_connect(video_t *video,
|
||||
const struct video_scale_info *conversion,
|
||||
void (*callback)(void *param, struct video_data *frame),
|
||||
void *param);
|
||||
EXPORT void video_output_disconnect(video_t *video,
|
||||
void (*callback)(void *param, struct video_data *frame),
|
||||
void *param);
|
||||
|
||||
EXPORT bool video_output_active(const video_t *video);
|
||||
|
||||
EXPORT const struct video_output_info *video_output_get_info(
|
||||
const video_t *video);
|
||||
EXPORT bool video_output_lock_frame(video_t *video, struct video_frame *frame,
|
||||
int count, uint64_t timestamp);
|
||||
EXPORT void video_output_unlock_frame(video_t *video);
|
||||
EXPORT uint64_t video_output_get_frame_time(const video_t *video);
|
||||
EXPORT void video_output_stop(video_t *video);
|
||||
EXPORT bool video_output_stopped(video_t *video);
|
||||
|
||||
EXPORT enum video_format video_output_get_format(const video_t *video);
|
||||
EXPORT uint32_t video_output_get_width(const video_t *video);
|
||||
EXPORT uint32_t video_output_get_height(const video_t *video);
|
||||
EXPORT double video_output_get_frame_rate(const video_t *video);
|
||||
|
||||
EXPORT uint32_t video_output_get_skipped_frames(const video_t *video);
|
||||
EXPORT uint32_t video_output_get_total_frames(const video_t *video);
|
||||
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
220
libobs/media-io/video-matrices.c
Normal file
220
libobs/media-io/video-matrices.c
Normal file
|
|
@ -0,0 +1,220 @@
|
|||
/******************************************************************************
|
||||
Copyright (C) 2014 by Ruwen Hahn <palana@stunned.de>
|
||||
|
||||
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 "../util/bmem.h"
|
||||
#include "video-io.h"
|
||||
|
||||
//#define COMPUTE_MATRICES
|
||||
|
||||
#ifdef COMPUTE_MATRICES
|
||||
#include "../graphics/matrix3.h"
|
||||
#endif
|
||||
|
||||
static struct {
|
||||
enum video_colorspace const color_space;
|
||||
float const Kb, Kr;
|
||||
int const range_min[3];
|
||||
int const range_max[3];
|
||||
int const black_levels[2][3];
|
||||
|
||||
float float_range_min[3];
|
||||
float float_range_max[3];
|
||||
float matrix[2][16];
|
||||
|
||||
} format_info[] = {
|
||||
{VIDEO_CS_601,
|
||||
0.114f, 0.299f, {16, 16, 16}, {235, 240, 240},
|
||||
{{16, 128, 128}, {0, 128, 128}},
|
||||
#ifndef COMPUTE_MATRICES
|
||||
{ 16.0f/255.0f, 16.0f/255.0f, 16.0f/255.0f},
|
||||
{235.0f/255.0f, 240.0f/255.0f, 240.0f/255.0f},
|
||||
{
|
||||
{
|
||||
1.164384f, 0.000000f, 1.596027f, -0.874202f,
|
||||
1.164384f, -0.391762f, -0.812968f, 0.531668f,
|
||||
1.164384f, 2.017232f, 0.000000f, -1.085631f,
|
||||
0.000000f, 0.000000f, 0.000000f, 1.000000f
|
||||
},
|
||||
{
|
||||
1.000000f, 0.000000f, 1.407520f, -0.706520f,
|
||||
1.000000f, -0.345491f, -0.716948f, 0.533303f,
|
||||
1.000000f, 1.778976f, 0.000000f, -0.892976f,
|
||||
0.000000f, 0.000000f, 0.000000f, 1.000000f
|
||||
}
|
||||
}
|
||||
#endif
|
||||
},
|
||||
{VIDEO_CS_709,
|
||||
0.0722f, 0.2126f, {16, 16, 16}, {235, 240, 240},
|
||||
{{16, 128, 128}, {0, 128, 128}},
|
||||
#ifndef COMPUTE_MATRICES
|
||||
{ 16.0f/255.0f, 16.0f/255.0f, 16.0f/255.0f},
|
||||
{235.0f/255.0f, 240.0f/255.0f, 240.0f/255.0f},
|
||||
{
|
||||
{
|
||||
1.164384f, 0.000000f, 1.792741f, -0.972945f,
|
||||
1.164384f, -0.213249f, -0.532909f, 0.301483f,
|
||||
1.164384f, 2.112402f, 0.000000f, -1.133402f,
|
||||
0.000000f, 0.000000f, 0.000000f, 1.000000f
|
||||
},
|
||||
{
|
||||
1.000000f, 0.000000f, 1.581000f, -0.793600f,
|
||||
1.000000f, -0.188062f, -0.469967f, 0.330305f,
|
||||
1.000000f, 1.862906f, 0.000000f, -0.935106f,
|
||||
0.000000f, 0.000000f, 0.000000f, 1.000000f
|
||||
}
|
||||
}
|
||||
#endif
|
||||
},
|
||||
};
|
||||
|
||||
#define NUM_FORMATS (sizeof(format_info)/sizeof(format_info[0]))
|
||||
|
||||
#ifdef COMPUTE_MATRICES
|
||||
static void log_matrix(float const matrix[16])
|
||||
{
|
||||
blog(LOG_DEBUG, "\n% f, % f, % f, % f" \
|
||||
"\n% f, % f, % f, % f" \
|
||||
"\n% f, % f, % f, % f" \
|
||||
"\n% f, % f, % f, % f",
|
||||
matrix[ 0], matrix[ 1], matrix[ 2], matrix[ 3],
|
||||
matrix[ 4], matrix[ 5], matrix[ 6], matrix[ 7],
|
||||
matrix[ 8], matrix[ 9], matrix[10], matrix[11],
|
||||
matrix[12], matrix[13], matrix[14], matrix[15]);
|
||||
}
|
||||
|
||||
static void initialize_matrix(float const Kb, float const Kr,
|
||||
int const range_min[3], int const range_max[3],
|
||||
int const black_levels[3], float matrix[16])
|
||||
{
|
||||
struct matrix3 color_matrix;
|
||||
|
||||
int yvals = range_max[0] - range_min[0];
|
||||
int uvals = (range_max[1] - range_min[1]) / 2;
|
||||
int vvals = (range_max[2] - range_min[2]) / 2;
|
||||
|
||||
vec3_set(&color_matrix.x, 255./yvals,
|
||||
0.,
|
||||
255./vvals * (1. - Kr));
|
||||
vec3_set(&color_matrix.y, 255./yvals,
|
||||
255./uvals * (Kb - 1.) * Kb / (1. - Kb - Kr),
|
||||
255./vvals * (Kr - 1.) * Kr / (1. - Kb - Kr));
|
||||
vec3_set(&color_matrix.z, 255./yvals,
|
||||
255./uvals * (1. - Kb),
|
||||
0.);
|
||||
|
||||
struct vec3 offsets, multiplied;
|
||||
vec3_set(&offsets,
|
||||
-black_levels[0]/255.,
|
||||
-black_levels[1]/255.,
|
||||
-black_levels[2]/255.);
|
||||
vec3_rotate(&multiplied, &offsets, &color_matrix);
|
||||
|
||||
matrix[ 0] = color_matrix.x.x;
|
||||
matrix[ 1] = color_matrix.x.y;
|
||||
matrix[ 2] = color_matrix.x.z;
|
||||
matrix[ 3] = multiplied.x;
|
||||
|
||||
matrix[ 4] = color_matrix.y.x;
|
||||
matrix[ 5] = color_matrix.y.y;
|
||||
matrix[ 6] = color_matrix.y.z;
|
||||
matrix[ 7] = multiplied.y;
|
||||
|
||||
matrix[ 8] = color_matrix.z.x;
|
||||
matrix[ 9] = color_matrix.z.y;
|
||||
matrix[10] = color_matrix.z.z;
|
||||
matrix[11] = multiplied.z;
|
||||
|
||||
matrix[12] = matrix[13] = matrix[14] = 0.;
|
||||
matrix[15] = 1.;
|
||||
|
||||
log_matrix(matrix);
|
||||
}
|
||||
|
||||
static void initialize_matrices()
|
||||
{
|
||||
static int range_min[] = { 0, 0, 0};
|
||||
static int range_max[] = {255, 255, 255};
|
||||
|
||||
for (size_t i = 0; i < NUM_FORMATS; i++) {
|
||||
initialize_matrix(format_info[i].Kb, format_info[i].Kr,
|
||||
range_min, range_max,
|
||||
format_info[i].black_levels[1],
|
||||
format_info[i].matrix[1]);
|
||||
|
||||
initialize_matrix(format_info[i].Kb, format_info[i].Kr,
|
||||
format_info[i].range_min,
|
||||
format_info[i].range_max,
|
||||
format_info[i].black_levels[0],
|
||||
format_info[i].matrix[0]);
|
||||
|
||||
for (int j = 0; j < 3; j++) {
|
||||
format_info[i].float_range_min[j] =
|
||||
format_info[i].range_min[j]/255.;
|
||||
format_info[i].float_range_max[j] =
|
||||
format_info[i].range_max[j]/255.;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
static bool matrices_initialized = false;
|
||||
#endif
|
||||
|
||||
static const float full_min[3] = {0.0f, 0.0f, 0.0f};
|
||||
static const float full_max[3] = {1.0f, 1.0f, 1.0f};
|
||||
|
||||
bool video_format_get_parameters(enum video_colorspace color_space,
|
||||
enum video_range_type range, float matrix[16],
|
||||
float range_min[3], float range_max[3])
|
||||
{
|
||||
#ifdef COMPUTE_MATRICES
|
||||
if (!matrices_initialized) {
|
||||
initialize_matrices();
|
||||
matrices_initialized = true;
|
||||
}
|
||||
#endif
|
||||
if (color_space == VIDEO_CS_DEFAULT)
|
||||
color_space = VIDEO_CS_601;
|
||||
|
||||
for (size_t i = 0; i < NUM_FORMATS; i++) {
|
||||
if (format_info[i].color_space != color_space)
|
||||
continue;
|
||||
|
||||
int full_range = range == VIDEO_RANGE_FULL ? 1 : 0;
|
||||
memcpy(matrix, format_info[i].matrix[full_range],
|
||||
sizeof(float) * 16);
|
||||
|
||||
if (range == VIDEO_RANGE_FULL) {
|
||||
if (range_min)
|
||||
memcpy(range_min, full_min, sizeof(float) * 3);
|
||||
if (range_max)
|
||||
memcpy(range_max, full_max, sizeof(float) * 3);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (range_min)
|
||||
memcpy(range_min, format_info[i].float_range_min,
|
||||
sizeof(float) * 3);
|
||||
|
||||
if (range_max)
|
||||
memcpy(range_max, format_info[i].float_range_max,
|
||||
sizeof(float) * 3);
|
||||
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
162
libobs/media-io/video-scaler-ffmpeg.c
Normal file
162
libobs/media-io/video-scaler-ffmpeg.c
Normal file
|
|
@ -0,0 +1,162 @@
|
|||
/******************************************************************************
|
||||
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 "../util/bmem.h"
|
||||
#include "video-scaler.h"
|
||||
|
||||
#include <libswscale/swscale.h>
|
||||
|
||||
struct video_scaler {
|
||||
struct SwsContext *swscale;
|
||||
int src_height;
|
||||
};
|
||||
|
||||
static inline enum AVPixelFormat get_ffmpeg_video_format(
|
||||
enum video_format format)
|
||||
{
|
||||
switch (format) {
|
||||
case VIDEO_FORMAT_NONE: return AV_PIX_FMT_NONE;
|
||||
case VIDEO_FORMAT_I420: return AV_PIX_FMT_YUV420P;
|
||||
case VIDEO_FORMAT_NV12: return AV_PIX_FMT_NV12;
|
||||
case VIDEO_FORMAT_YVYU: return AV_PIX_FMT_NONE;
|
||||
case VIDEO_FORMAT_YUY2: return AV_PIX_FMT_YUYV422;
|
||||
case VIDEO_FORMAT_UYVY: return AV_PIX_FMT_UYVY422;
|
||||
case VIDEO_FORMAT_RGBA: return AV_PIX_FMT_RGBA;
|
||||
case VIDEO_FORMAT_BGRA: return AV_PIX_FMT_BGRA;
|
||||
case VIDEO_FORMAT_BGRX: return AV_PIX_FMT_BGRA;
|
||||
case VIDEO_FORMAT_I444: return AV_PIX_FMT_YUV444P;
|
||||
}
|
||||
|
||||
return AV_PIX_FMT_NONE;
|
||||
}
|
||||
|
||||
static inline int get_ffmpeg_scale_type(enum video_scale_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case VIDEO_SCALE_DEFAULT: return SWS_FAST_BILINEAR;
|
||||
case VIDEO_SCALE_POINT: return SWS_POINT;
|
||||
case VIDEO_SCALE_FAST_BILINEAR: return SWS_FAST_BILINEAR;
|
||||
case VIDEO_SCALE_BILINEAR: return SWS_BILINEAR | SWS_AREA;
|
||||
case VIDEO_SCALE_BICUBIC: return SWS_BICUBIC;
|
||||
}
|
||||
|
||||
return SWS_POINT;
|
||||
}
|
||||
|
||||
static inline const int *get_ffmpeg_coeffs(enum video_colorspace cs)
|
||||
{
|
||||
switch (cs) {
|
||||
case VIDEO_CS_DEFAULT: return sws_getCoefficients(SWS_CS_ITU601);
|
||||
case VIDEO_CS_601: return sws_getCoefficients(SWS_CS_ITU601);
|
||||
case VIDEO_CS_709: return sws_getCoefficients(SWS_CS_ITU709);
|
||||
}
|
||||
|
||||
return sws_getCoefficients(SWS_CS_ITU601);
|
||||
}
|
||||
|
||||
static inline int get_ffmpeg_range_type(enum video_range_type type)
|
||||
{
|
||||
switch (type) {
|
||||
case VIDEO_RANGE_DEFAULT: return 0;
|
||||
case VIDEO_RANGE_PARTIAL: return 0;
|
||||
case VIDEO_RANGE_FULL: return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
#define FIXED_1_0 (1<<16)
|
||||
|
||||
int video_scaler_create(video_scaler_t **scaler_out,
|
||||
const struct video_scale_info *dst,
|
||||
const struct video_scale_info *src,
|
||||
enum video_scale_type type)
|
||||
{
|
||||
enum AVPixelFormat format_src = get_ffmpeg_video_format(src->format);
|
||||
enum AVPixelFormat format_dst = get_ffmpeg_video_format(dst->format);
|
||||
int scale_type = get_ffmpeg_scale_type(type);
|
||||
const int *coeff_src = get_ffmpeg_coeffs(src->colorspace);
|
||||
const int *coeff_dst = get_ffmpeg_coeffs(dst->colorspace);
|
||||
int range_src = get_ffmpeg_range_type(src->range);
|
||||
int range_dst = get_ffmpeg_range_type(dst->range);
|
||||
struct video_scaler *scaler;
|
||||
int ret;
|
||||
|
||||
if (!scaler_out)
|
||||
return VIDEO_SCALER_FAILED;
|
||||
|
||||
if (format_src == AV_PIX_FMT_NONE ||
|
||||
format_dst == AV_PIX_FMT_NONE)
|
||||
return VIDEO_SCALER_BAD_CONVERSION;
|
||||
|
||||
scaler = bzalloc(sizeof(struct video_scaler));
|
||||
scaler->src_height = src->height;
|
||||
|
||||
scaler->swscale = sws_getCachedContext(NULL,
|
||||
src->width, src->height, format_src,
|
||||
dst->width, dst->height, format_dst,
|
||||
scale_type, NULL, NULL, NULL);
|
||||
if (!scaler->swscale) {
|
||||
blog(LOG_ERROR, "video_scaler_create: Could not create "
|
||||
"swscale");
|
||||
goto fail;
|
||||
}
|
||||
|
||||
ret = sws_setColorspaceDetails(scaler->swscale,
|
||||
coeff_src, range_src,
|
||||
coeff_dst, range_dst,
|
||||
0, FIXED_1_0, FIXED_1_0);
|
||||
if (ret < 0) {
|
||||
blog(LOG_DEBUG, "video_scaler_create: "
|
||||
"sws_setColorspaceDetails failed, ignoring");
|
||||
}
|
||||
|
||||
*scaler_out = scaler;
|
||||
return VIDEO_SCALER_SUCCESS;
|
||||
|
||||
fail:
|
||||
video_scaler_destroy(scaler);
|
||||
return VIDEO_SCALER_FAILED;
|
||||
}
|
||||
|
||||
void video_scaler_destroy(video_scaler_t *scaler)
|
||||
{
|
||||
if (scaler) {
|
||||
sws_freeContext(scaler->swscale);
|
||||
bfree(scaler);
|
||||
}
|
||||
}
|
||||
|
||||
bool video_scaler_scale(video_scaler_t *scaler,
|
||||
uint8_t *output[], const uint32_t out_linesize[],
|
||||
const uint8_t *const input[], const uint32_t in_linesize[])
|
||||
{
|
||||
if (!scaler)
|
||||
return false;
|
||||
|
||||
int ret = sws_scale(scaler->swscale,
|
||||
input, (const int *)in_linesize,
|
||||
0, scaler->src_height,
|
||||
output, (const int *)out_linesize);
|
||||
if (ret <= 0) {
|
||||
blog(LOG_ERROR, "video_scaler_scale: sws_scale failed: %d",
|
||||
ret);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
46
libobs/media-io/video-scaler.h
Normal file
46
libobs/media-io/video-scaler.h
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
/******************************************************************************
|
||||
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
|
||||
|
||||
#include "../util/c99defs.h"
|
||||
#include "video-io.h"
|
||||
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
struct video_scaler;
|
||||
typedef struct video_scaler video_scaler_t;
|
||||
|
||||
#define VIDEO_SCALER_SUCCESS 0
|
||||
#define VIDEO_SCALER_BAD_CONVERSION -1
|
||||
#define VIDEO_SCALER_FAILED -2
|
||||
|
||||
EXPORT int video_scaler_create(video_scaler_t **scaler,
|
||||
const struct video_scale_info *dst,
|
||||
const struct video_scale_info *src,
|
||||
enum video_scale_type type);
|
||||
EXPORT void video_scaler_destroy(video_scaler_t *scaler);
|
||||
|
||||
EXPORT bool video_scaler_scale(video_scaler_t *scaler,
|
||||
uint8_t *output[], const uint32_t out_linesize[],
|
||||
const uint8_t *const input[], const uint32_t in_linesize[]);
|
||||
|
||||
#ifdef __cplusplus
|
||||
}
|
||||
#endif
|
||||
Loading…
Add table
Add a link
Reference in a new issue