Integrate record_driver.c into retroarch.c

This commit is contained in:
twinaphex 2019-06-17 16:23:46 +02:00
parent ee2745d1bd
commit f061941456
21 changed files with 741 additions and 955 deletions

View File

@ -260,7 +260,6 @@ OBJ += frontend/frontend.o \
input/drivers_hid/null_hid.o \
input/drivers_joypad/null_joypad.o \
playlist.o \
record/record_driver.o \
record/drivers/record_null.o \
$(LIBRETRO_COMM_DIR)/features/features_cpu.o \
performance_counters.o \

View File

@ -74,7 +74,6 @@
#include "driver.h"
#include "input/input_driver.h"
#include "frontend/frontend_driver.h"
#include "record/record_driver.h"
#include "file_path_special.h"
#include "autosave.h"
#include "core_info.h"

View File

@ -54,8 +54,6 @@
#include "../list_special.h"
#include "record/record_driver.h"
#if defined(__WINRT__) || defined(WINAPI_FAMILY) && WINAPI_FAMILY == WINAPI_FAMILY_PHONE_APP
#include "uwp/uwp_func.h"
#endif

View File

@ -52,7 +52,6 @@
#include "dynamic.h"
#include "command.h"
#include "record/record_driver.h"
#include "driver.h"
#include "performance_counters.h"
#include "led/led_driver.h"

View File

@ -38,13 +38,12 @@
#include "../paths.h"
#include "../retroarch.h"
#include "../verbosity.h"
#include "../record/record_driver.h"
#if defined(_WIN32) && !defined(_XBOX) && !defined(__WINRT__)
#include <objbase.h>
#endif
/* griffin hack */
/* Griffin hack */
#ifdef HAVE_QT
#ifndef HAVE_MAIN
#define HAVE_MAIN

View File

@ -50,7 +50,6 @@
#include "../../configuration.h"
#include "../../dynamic.h"
#include "../../record/record_driver.h"
#include "../../retroarch.h"
#include "../../verbosity.h"

View File

@ -33,7 +33,6 @@
#include "../../configuration.h"
#include "../../dynamic.h"
#include "../../record/record_driver.h"
#include "../../managers/state_manager.h"
#include "../../retroarch.h"

View File

@ -48,7 +48,6 @@
#import "../../driver.h"
#import "../../configuration.h"
#import "../../record/record_driver.h"
#import "../../retroarch.h"
#import "../../verbosity.h"

View File

@ -47,7 +47,6 @@
#include "../../driver.h"
#include "../../configuration.h"
#include "../../record/record_driver.h"
#include "../../managers/state_manager.h"
#include "../../retroarch.h"

View File

@ -1,75 +0,0 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
*
* RetroArch 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 Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch 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 RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <stddef.h>
#include <string.h>
#include <compat/strl.h>
#include <retro_common_api.h>
#include <file/config_file.h>
#include <features/features_cpu.h>
#include <file/file_path.h>
#include <string/stdstring.h>
#include <retro_math.h>
#include <retro_miscellaneous.h>
#include <retro_assert.h>
#include <gfx/scaler/pixconv.h>
#include <gfx/scaler/scaler.h>
#include <gfx/video_frame.h>
#include <formats/image.h>
#include "../menu/menu_shader.h"
#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif
#include "../dynamic.h"
#ifdef HAVE_THREADS
#include <rthreads/rthreads.h>
#endif
#ifdef HAVE_MENU
#include "../menu/menu_driver.h"
#include "../menu/menu_setting.h"
#ifdef HAVE_MENU_WIDGETS
#include "../menu/widgets/menu_widgets.h"
#endif
#endif
#ifdef HAVE_VIDEO_LAYOUT
#include "video_layout.h"
#endif
#include "video_thread_wrapper.h"
#include "video_driver.h"
#include "video_display_server.h"
#include "video_crt_switch.h"
#include "../frontend/frontend_driver.h"
#include "../record/record_driver.h"
#include "../config.def.h"
#include "../configuration.h"
#include "../driver.h"
#include "../retroarch.h"
#include "../input/input_driver.h"
#include "../list_special.h"
#include "../core.h"
#include "../command.h"
#include "../msg_hash.h"
#include "../verbosity.h"

View File

@ -1120,7 +1120,6 @@ WIFI
/*============================================================
RECORDING
============================================================ */
#include "../record/record_driver.c"
#include "../record/drivers/record_null.c"
#ifdef HAVE_FFMPEG

View File

@ -39,7 +39,6 @@
#include "frontend/frontend_driver.h"
#include "core_info.h"
#include "input/input_driver.h"
#include "record/record_driver.h"
#include "midi/midi_driver.h"
#include "configuration.h"
#include "retroarch.h"

View File

@ -79,8 +79,6 @@
#include "../cheevos-new/cheevos.h"
#endif
#include "../../record/record_driver.h"
#ifdef __WINRT__
#include "../../uwp/uwp_func.h"
#endif

View File

@ -76,7 +76,6 @@
#include "../managers/cheat_manager.h"
#include "../managers/core_option_manager.h"
#include "../paths.h"
#include "../record/record_driver.h"
#include "../retroarch.h"
#include "../core.h"
#include "../frontend/frontend_driver.h"

View File

@ -75,7 +75,6 @@
#include "../dynamic.h"
#include "../list_special.h"
#include "../wifi/wifi_driver.h"
#include "../record/record_driver.h"
#include "../input/input_driver.h"
#include "../midi/midi_driver.h"
#include "../tasks/tasks_internal.h"

View File

@ -64,8 +64,6 @@ extern "C" {
}
#endif
#include "../record_driver.h"
#include "../../configuration.h"
#include "../../retroarch.h"
#include "../../verbosity.h"

View File

@ -25,7 +25,7 @@
#include "../../config.h"
#endif
#include "../record_driver.h"
#include "../../retroarch.h"
static void record_null_free(void *data)
{

View File

@ -1,595 +0,0 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
*
* RetroArch 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 Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch 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 RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#include <string.h>
#include <file/file_path.h>
#include <compat/strl.h>
#include <string/stdstring.h>
#include <retro_math.h>
#ifdef HAVE_CONFIG_H
#include "../config.h"
#endif
#include "record_driver.h"
#include "../command.h"
#include "../configuration.h"
#include "../driver.h"
#include "../retroarch.h"
#include "../verbosity.h"
#include "../msg_hash.h"
#include "../list_special.h"
#include "../paths.h"
static const record_driver_t *record_drivers[] = {
#ifdef HAVE_FFMPEG
&record_ffmpeg,
#endif
&record_null,
NULL,
};
unsigned recording_width = 0;
unsigned recording_height = 0;
size_t recording_gpu_width = 0;
size_t recording_gpu_height = 0;
static bool recording_enable = false;
static bool streaming_enable = false;
static const record_driver_t *recording_driver = NULL;
void *recording_data = NULL;
/**
* record_driver_find_ident:
* @idx : index of driver to get handle to.
*
* Returns: Human-readable identifier of record driver at index. Can be NULL
* if nothing found.
**/
const char *record_driver_find_ident(int idx)
{
const record_driver_t *drv = record_drivers[idx];
if (!drv)
return NULL;
return drv->ident;
}
/**
* record_driver_find_handle:
* @idx : index of driver to get handle to.
*
* Returns: handle to record driver at index. Can be NULL
* if nothing found.
**/
const void *record_driver_find_handle(int idx)
{
const void *drv = record_drivers[idx];
if (!drv)
return NULL;
return drv;
}
/**
* config_get_record_driver_options:
*
* Get an enumerated list of all record driver names, separated by '|'.
*
* Returns: string listing of all record driver names, separated by '|'.
**/
const char* config_get_record_driver_options(void)
{
return char_list_new_special(STRING_LIST_RECORD_DRIVERS, NULL);
}
void find_record_driver(void)
{
int i;
driver_ctx_info_t drv;
settings_t *settings = config_get_ptr();
drv.label = "record_driver";
drv.s = settings->arrays.record_driver;
driver_ctl(RARCH_DRIVER_CTL_FIND_INDEX, &drv);
i = (int)drv.len;
if (i >= 0)
recording_driver = (const record_driver_t*)record_driver_find_handle(i);
else
{
if (verbosity_is_enabled())
{
unsigned d;
RARCH_ERR("[recording] Couldn't find any record driver named \"%s\"\n",
settings->arrays.record_driver);
RARCH_LOG_OUTPUT("Available record drivers are:\n");
for (d = 0; record_driver_find_handle(d); d++)
RARCH_LOG_OUTPUT("\t%s\n", record_driver_find_ident(d));
RARCH_WARN("[recording] Going to default to first record driver...\n");
}
recording_driver = (const record_driver_t*)record_driver_find_handle(0);
if (!recording_driver)
retroarch_fail(1, "find_record_driver()");
}
}
/**
* ffemu_find_backend:
* @ident : Identifier of driver to find.
*
* Finds a recording driver with the name @ident.
*
* Returns: recording driver handle if successful, otherwise
* NULL.
**/
const record_driver_t *ffemu_find_backend(const char *ident)
{
unsigned i;
for (i = 0; record_drivers[i]; i++)
{
if (string_is_equal(record_drivers[i]->ident, ident))
return record_drivers[i];
}
return NULL;
}
/**
* gfx_ctx_init_first:
* @backend : Recording backend handle.
* @data : Recording data handle.
* @params : Recording info parameters.
*
* Finds first suitable recording context driver and initializes.
*
* Returns: true (1) if successful, otherwise false (0).
**/
bool record_driver_init_first(const record_driver_t **backend, void **data,
const struct record_params *params)
{
unsigned i;
for (i = 0; record_drivers[i]; i++)
{
void *handle = record_drivers[i]->init(params);
if (!handle)
continue;
*backend = record_drivers[i];
*data = handle;
return true;
}
return false;
}
void recording_dump_frame(const void *data, unsigned width,
unsigned height, size_t pitch, bool is_idle)
{
bool has_gpu_record = false;
uint8_t *gpu_buf = NULL;
struct record_video_data ffemu_data = {0};
video_driver_get_record_status(&has_gpu_record,
&gpu_buf);
ffemu_data.pitch = (int)pitch;
ffemu_data.width = width;
ffemu_data.height = height;
ffemu_data.data = data;
if (has_gpu_record)
{
struct video_viewport vp;
vp.x = 0;
vp.y = 0;
vp.width = 0;
vp.height = 0;
vp.full_width = 0;
vp.full_height = 0;
video_driver_get_viewport_info(&vp);
if (!vp.width || !vp.height)
{
RARCH_WARN("[recording] %s \n",
msg_hash_to_str(MSG_VIEWPORT_SIZE_CALCULATION_FAILED));
command_event(CMD_EVENT_GPU_RECORD_DEINIT, NULL);
recording_dump_frame(data, width, height, pitch, is_idle);
return;
}
/* User has resized. We kinda have a problem now. */
if ( vp.width != recording_gpu_width ||
vp.height != recording_gpu_height)
{
RARCH_WARN("[recording] %s\n", msg_hash_to_str(MSG_RECORDING_TERMINATED_DUE_TO_RESIZE));
runloop_msg_queue_push(
msg_hash_to_str(MSG_RECORDING_TERMINATED_DUE_TO_RESIZE),
1, 180, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
return;
}
if (!gpu_buf)
return;
/* Big bottleneck.
* Since we might need to do read-backs asynchronously,
* it might take 3-4 times before this returns true. */
if (!video_driver_read_viewport(gpu_buf, is_idle))
return;
ffemu_data.pitch = (int)(recording_gpu_width * 3);
ffemu_data.width = (unsigned)recording_gpu_width;
ffemu_data.height = (unsigned)recording_gpu_height;
ffemu_data.data = gpu_buf + (ffemu_data.height - 1) * ffemu_data.pitch;
ffemu_data.pitch = -ffemu_data.pitch;
}
if (!has_gpu_record)
ffemu_data.is_dupe = !data;
if (recording_driver && recording_driver->push_video)
recording_driver->push_video(recording_data, &ffemu_data);
}
bool recording_deinit(void)
{
if (!recording_data || !recording_driver)
return false;
if (recording_driver->finalize)
recording_driver->finalize(recording_data);
if (recording_driver->free)
recording_driver->free(recording_data);
recording_data = NULL;
recording_driver = NULL;
command_event(CMD_EVENT_GPU_RECORD_DEINIT, NULL);
return true;
}
bool recording_is_enabled(void)
{
return recording_enable;
}
void recording_set_state(bool state)
{
recording_enable = state;
}
bool streaming_is_enabled(void)
{
return streaming_enable;
}
void streaming_set_state(bool state)
{
streaming_enable = state;
}
void recording_push_audio(const int16_t *data, size_t samples)
{
struct record_audio_data ffemu_data;
ffemu_data.data = data;
ffemu_data.frames = samples / 2;
if (recording_driver && recording_driver->push_audio)
recording_driver->push_audio(recording_data, &ffemu_data);
}
/**
* recording_init:
*
* Initializes recording.
*
* Returns: true (1) if successful, otherwise false (0).
**/
bool recording_init(void)
{
char output[PATH_MAX_LENGTH];
char buf[PATH_MAX_LENGTH];
struct record_params params = {0};
struct retro_system_av_info *av_info = video_viewport_get_system_av_info();
bool recording_enabled = recording_is_enabled();
settings_t *settings = config_get_ptr();
global_t *global = global_get_ptr();
if (!recording_enabled)
return false;
output[0] = '\0';
if (rarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL))
{
RARCH_WARN("[recording] %s\n",
msg_hash_to_str(MSG_USING_LIBRETRO_DUMMY_CORE_RECORDING_SKIPPED));
return false;
}
if (!settings->bools.video_gpu_record
&& video_driver_is_hw_context())
{
RARCH_WARN("[recording] %s.\n",
msg_hash_to_str(MSG_HW_RENDERED_MUST_USE_POSTSHADED_RECORDING));
return false;
}
RARCH_LOG("[recording] %s: FPS: %.4f, Sample rate: %.4f\n",
msg_hash_to_str(MSG_CUSTOM_TIMING_GIVEN),
(float)av_info->timing.fps,
(float)av_info->timing.sample_rate);
if (!string_is_empty(global->record.path))
strlcpy(output, global->record.path, sizeof(output));
else
{
if(streaming_is_enabled())
if (!string_is_empty(settings->paths.path_stream_url))
strlcpy(output, settings->paths.path_stream_url, sizeof(output));
else
/* Fallback, stream locally to 127.0.0.1 */
snprintf(output, sizeof(output), "udp://127.0.0.1:%u", settings->uints.video_stream_port);
else
{
const char *game_name = path_basename(path_get(RARCH_PATH_BASENAME));
if (settings->uints.video_record_quality < RECORD_CONFIG_TYPE_RECORDING_WEBM_FAST)
{
fill_str_dated_filename(buf, game_name,
"mkv", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
else if (settings->uints.video_record_quality >= RECORD_CONFIG_TYPE_RECORDING_WEBM_FAST && settings->uints.video_record_quality < RECORD_CONFIG_TYPE_RECORDING_GIF)
{
fill_str_dated_filename(buf, game_name,
"webm", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
else if (settings->uints.video_record_quality >= RECORD_CONFIG_TYPE_RECORDING_GIF && settings->uints.video_record_quality < RECORD_CONFIG_TYPE_RECORDING_APNG)
{
fill_str_dated_filename(buf, game_name,
"gif", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
else
{
fill_str_dated_filename(buf, game_name,
"png", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
}
}
params.out_width = av_info->geometry.base_width;
params.out_height = av_info->geometry.base_height;
params.fb_width = av_info->geometry.max_width;
params.fb_height = av_info->geometry.max_height;
params.channels = 2;
params.filename = output;
params.fps = av_info->timing.fps;
params.samplerate = av_info->timing.sample_rate;
params.pix_fmt = (video_driver_get_pixel_format() == RETRO_PIXEL_FORMAT_XRGB8888) ?
FFEMU_PIX_ARGB8888 : FFEMU_PIX_RGB565;
params.config = NULL;
if (!string_is_empty(global->record.config))
params.config = global->record.config;
else
{
if (streaming_is_enabled())
{
params.config = settings->paths.path_stream_config;
params.preset = (enum record_config_type)
settings->uints.video_stream_quality;
}
else
{
params.config = settings->paths.path_record_config;
params.preset = (enum record_config_type)
settings->uints.video_record_quality;
}
}
if (video_driver_supports_recording())
{
unsigned gpu_size;
struct video_viewport vp;
vp.x = 0;
vp.y = 0;
vp.width = 0;
vp.height = 0;
vp.full_width = 0;
vp.full_height = 0;
video_driver_get_viewport_info(&vp);
if (!vp.width || !vp.height)
{
RARCH_ERR("[recording] Failed to get viewport information from video driver. "
"Cannot start recording ...\n");
return false;
}
params.out_width = vp.width;
params.out_height = vp.height;
params.fb_width = next_pow2(vp.width);
params.fb_height = next_pow2(vp.height);
if (settings->bools.video_force_aspect &&
(video_driver_get_aspect_ratio() > 0.0f))
params.aspect_ratio = video_driver_get_aspect_ratio();
else
params.aspect_ratio = (float)vp.width / vp.height;
params.pix_fmt = FFEMU_PIX_BGR24;
recording_gpu_width = vp.width;
recording_gpu_height = vp.height;
RARCH_LOG("[recording] %s %u x %u\n", msg_hash_to_str(MSG_DETECTED_VIEWPORT_OF),
vp.width, vp.height);
gpu_size = vp.width * vp.height * 3;
if (!video_driver_gpu_record_init(gpu_size))
return false;
}
else
{
if (recording_width || recording_height)
{
params.out_width = recording_width;
params.out_height = recording_height;
}
if (settings->bools.video_force_aspect &&
(video_driver_get_aspect_ratio() > 0.0f))
params.aspect_ratio = video_driver_get_aspect_ratio();
else
params.aspect_ratio = (float)params.out_width / params.out_height;
if (settings->bools.video_post_filter_record
&& video_driver_frame_filter_alive())
{
unsigned max_width = 0;
unsigned max_height = 0;
params.pix_fmt = FFEMU_PIX_RGB565;
if (video_driver_frame_filter_is_32bit())
params.pix_fmt = FFEMU_PIX_ARGB8888;
rarch_softfilter_get_max_output_size(
video_driver_frame_filter_get_ptr(),
&max_width, &max_height);
params.fb_width = next_pow2(max_width);
params.fb_height = next_pow2(max_height);
}
}
RARCH_LOG("[recording] %s %s @ %ux%u. (FB size: %ux%u pix_fmt: %u)\n",
msg_hash_to_str(MSG_RECORDING_TO),
output,
params.out_width, params.out_height,
params.fb_width, params.fb_height,
(unsigned)params.pix_fmt);
if (!record_driver_init_first(&recording_driver, &recording_data, &params))
{
RARCH_ERR("[recording] %s\n", msg_hash_to_str(MSG_FAILED_TO_START_RECORDING));
command_event(CMD_EVENT_GPU_RECORD_DEINIT, NULL);
return false;
}
return true;
}
void *recording_driver_get_data_ptr(void)
{
return recording_data;
}
void recording_driver_clear_data_ptr(void)
{
recording_data = NULL;
}
void recording_driver_set_data_ptr(void *data)
{
recording_data = data;
}
void recording_driver_get_size(unsigned *width, unsigned *height)
{
*width = recording_width;
*height = recording_height;
}
void recording_driver_update_streaming_url(void)
{
settings_t *settings = config_get_ptr();
const char* youtube_url = "rtmp://a.rtmp.youtube.com/live2/";
const char* twitch_url = "rtmp://live.twitch.tv/app/";
if (!settings)
return;
switch (settings->uints.streaming_mode)
{
case STREAMING_MODE_TWITCH:
{
if (!string_is_empty(settings->arrays.twitch_stream_key))
snprintf(settings->paths.path_stream_url, sizeof(settings->paths.path_stream_url),
"%s%s", twitch_url, settings->arrays.twitch_stream_key);
else
{
/* To-Do: Show input box for twitch_stream_key*/
RARCH_LOG("[recording] twitch streaming key empty\n");
}
break;
}
case STREAMING_MODE_YOUTUBE:
{
if (!string_is_empty(settings->arrays.youtube_stream_key))
{
snprintf(settings->paths.path_stream_url, sizeof(settings->paths.path_stream_url),
"%s%s", youtube_url, settings->arrays.youtube_stream_key);
}
else
{
/* To-Do: Show input box for youtube_stream_key*/
RARCH_LOG("[recording] youtube streaming key empty\n");
}
break;
}
case STREAMING_MODE_LOCAL:
/* To-Do: figure out default interface and bind to that instead */
snprintf(settings->paths.path_stream_url, sizeof(settings->paths.path_stream_url),
"udp://%s:%u", "127.0.0.1", settings->uints.video_stream_port);
break;
case STREAMING_MODE_CUSTOM:
default:
/* Do nothing, let the user input the URL */
break;
}
}
void recording_driver_free_state(void)
{
recording_gpu_width = 0;
recording_gpu_height = 0;
recording_width = 0;
recording_height = 0;
}

View File

@ -1,220 +0,0 @@
/* RetroArch - A frontend for libretro.
* Copyright (C) 2010-2014 - Hans-Kristian Arntzen
* Copyright (C) 2011-2017 - Daniel De Matteis
*
* RetroArch 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 Found-
* ation, either version 3 of the License, or (at your option) any later version.
*
* RetroArch 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 RetroArch.
* If not, see <http://www.gnu.org/licenses/>.
*/
#ifndef __RECORD_DRIVER_H
#define __RECORD_DRIVER_H
#include <stdint.h>
#include <stddef.h>
#include <boolean.h>
#include <retro_common_api.h>
RETRO_BEGIN_DECLS
enum ffemu_pix_format
{
FFEMU_PIX_RGB565 = 0,
FFEMU_PIX_BGR24,
FFEMU_PIX_ARGB8888
};
enum streaming_mode
{
STREAMING_MODE_TWITCH = 0,
STREAMING_MODE_YOUTUBE,
STREAMING_MODE_LOCAL,
STREAMING_MODE_CUSTOM
};
enum record_config_type
{
RECORD_CONFIG_TYPE_RECORDING_CUSTOM = 0,
RECORD_CONFIG_TYPE_RECORDING_LOW_QUALITY,
RECORD_CONFIG_TYPE_RECORDING_MED_QUALITY,
RECORD_CONFIG_TYPE_RECORDING_HIGH_QUALITY,
RECORD_CONFIG_TYPE_RECORDING_LOSSLESS_QUALITY,
RECORD_CONFIG_TYPE_RECORDING_WEBM_FAST,
RECORD_CONFIG_TYPE_RECORDING_WEBM_HIGH_QUALITY,
RECORD_CONFIG_TYPE_RECORDING_GIF,
RECORD_CONFIG_TYPE_RECORDING_APNG,
RECORD_CONFIG_TYPE_STREAMING_CUSTOM,
RECORD_CONFIG_TYPE_STREAMING_LOW_QUALITY,
RECORD_CONFIG_TYPE_STREAMING_MED_QUALITY,
RECORD_CONFIG_TYPE_STREAMING_HIGH_QUALITY,
RECORD_CONFIG_TYPE_STREAMING_NETPLAY
};
/* Parameters passed to ffemu_new() */
struct record_params
{
/* Framerate per second of input video. */
double fps;
/* Sample rate of input audio. */
double samplerate;
/* Desired output resolution. */
unsigned out_width;
unsigned out_height;
/* Total size of framebuffer used in input. */
unsigned fb_width;
unsigned fb_height;
/* Aspect ratio of input video. Parameters are passed to the muxer,
* the video itself is not scaled.
*/
float aspect_ratio;
/* Audio channels. */
unsigned channels;
enum record_config_type preset;
/* Input pixel format. */
enum ffemu_pix_format pix_fmt;
/* Filename to dump to. */
const char *filename;
/* Path to config. Optional. */
const char *config;
};
struct record_video_data
{
const void *data;
unsigned width;
unsigned height;
int pitch;
bool is_dupe;
};
struct record_audio_data
{
const void *data;
size_t frames;
};
typedef struct record_driver
{
void *(*init)(const struct record_params *params);
void (*free)(void *data);
bool (*push_video)(void *data, const struct record_video_data *video_data);
bool (*push_audio)(void *data, const struct record_audio_data *audio_data);
bool (*finalize)(void *data);
const char *ident;
} record_driver_t;
extern const record_driver_t record_ffmpeg;
extern const record_driver_t record_null;
/**
* config_get_record_driver_options:
*
* Get an enumerated list of all record driver names, separated by '|'.
*
* Returns: string listing of all record driver names, separated by '|'.
**/
const char* config_get_record_driver_options(void);
/**
* ffemu_find_backend:
* @ident : Identifier of driver to find.
*
* Finds a recording driver with the name @ident.
*
* Returns: recording driver handle if successful, otherwise
* NULL.
**/
const record_driver_t *ffemu_find_backend(const char *ident);
/**
* record_driver_find_handle:
* @idx : index of driver to get handle to.
*
* Returns: handle to record driver at index. Can be NULL
* if nothing found.
**/
const void *record_driver_find_handle(int idx);
/**
* record_driver_find_ident:
* @idx : index of driver to get handle to.
*
* Returns: Human-readable identifier of record driver at index. Can be NULL
* if nothing found.
**/
const char *record_driver_find_ident(int idx);
/**
* gfx_ctx_init_first:
* @backend : Recording backend handle.
* @data : Recording data handle.
* @params : Recording info parameters.
*
* Finds first suitable recording context driver and initializes.
*
* Returns: true (1) if successful, otherwise false (0).
**/
bool record_driver_init_first(const record_driver_t **backend, void **data,
const struct record_params *params);
void recording_dump_frame(const void *data, unsigned width,
unsigned height, size_t pitch, bool is_idle);
bool recording_deinit(void);
void find_record_driver(void);
/**
* recording_init:
*
* Initializes recording.
*
* Returns: true (1) if successful, otherwise false (0).
**/
bool recording_init(void);
bool recording_is_enabled(void);
void recording_set_state(bool state);
void streaming_set_state(bool state);
void recording_push_audio(const int16_t *data, size_t samples);
void *recording_driver_get_data_ptr(void);
void recording_driver_clear_data_ptr(void);
void recording_driver_set_data_ptr(void *data);
void recording_driver_free_state(void);
void recording_driver_get_size(unsigned *width, unsigned *height);
bool recording_is_enabled(void);
bool streaming_is_enabled(void);
void recording_driver_update_streaming_url(void);
extern void *recording_data;
RETRO_END_DECLS
#endif

View File

@ -130,7 +130,6 @@
#endif
#include "../gfx/video_display_server.h"
#include "../gfx/video_crt_switch.h"
#include "record/record_driver.h"
#include "wifi/wifi_driver.h"
#include "led/led_driver.h"
#include "midi/midi_driver.h"
@ -361,6 +360,536 @@ static void retroarch_msg_queue_init(void)
#endif
}
/* Recording */
static const record_driver_t *record_drivers[] = {
#ifdef HAVE_FFMPEG
&record_ffmpeg,
#endif
&record_null,
NULL,
};
unsigned recording_width = 0;
unsigned recording_height = 0;
size_t recording_gpu_width = 0;
size_t recording_gpu_height = 0;
static bool recording_enable = false;
static bool streaming_enable = false;
static const record_driver_t *recording_driver = NULL;
static void *recording_data = NULL;
static uint8_t *video_driver_record_gpu_buffer = NULL;
/**
* record_driver_find_ident:
* @idx : index of driver to get handle to.
*
* Returns: Human-readable identifier of record driver at index. Can be NULL
* if nothing found.
**/
const char *record_driver_find_ident(int idx)
{
const record_driver_t *drv = record_drivers[idx];
if (!drv)
return NULL;
return drv->ident;
}
/**
* record_driver_find_handle:
* @idx : index of driver to get handle to.
*
* Returns: handle to record driver at index. Can be NULL
* if nothing found.
**/
const void *record_driver_find_handle(int idx)
{
const void *drv = record_drivers[idx];
if (!drv)
return NULL;
return drv;
}
/**
* config_get_record_driver_options:
*
* Get an enumerated list of all record driver names, separated by '|'.
*
* Returns: string listing of all record driver names, separated by '|'.
**/
const char* config_get_record_driver_options(void)
{
return char_list_new_special(STRING_LIST_RECORD_DRIVERS, NULL);
}
#if 0
/* TODO/FIXME - not used apparently */
static void find_record_driver(void)
{
int i;
driver_ctx_info_t drv;
settings_t *settings = config_get_ptr();
drv.label = "record_driver";
drv.s = settings->arrays.record_driver;
driver_ctl(RARCH_DRIVER_CTL_FIND_INDEX, &drv);
i = (int)drv.len;
if (i >= 0)
recording_driver = (const record_driver_t*)record_driver_find_handle(i);
else
{
if (verbosity_is_enabled())
{
unsigned d;
RARCH_ERR("[recording] Couldn't find any record driver named \"%s\"\n",
settings->arrays.record_driver);
RARCH_LOG_OUTPUT("Available record drivers are:\n");
for (d = 0; record_driver_find_handle(d); d++)
RARCH_LOG_OUTPUT("\t%s\n", record_driver_find_ident(d));
RARCH_WARN("[recording] Going to default to first record driver...\n");
}
recording_driver = (const record_driver_t*)record_driver_find_handle(0);
if (!recording_driver)
retroarch_fail(1, "find_record_driver()");
}
}
/**
* ffemu_find_backend:
* @ident : Identifier of driver to find.
*
* Finds a recording driver with the name @ident.
*
* Returns: recording driver handle if successful, otherwise
* NULL.
**/
static const record_driver_t *ffemu_find_backend(const char *ident)
{
unsigned i;
for (i = 0; record_drivers[i]; i++)
{
if (string_is_equal(record_drivers[i]->ident, ident))
return record_drivers[i];
}
return NULL;
}
static void recording_driver_free_state(void)
{
/* TODO/FIXME - this is not being called anywhere */
recording_gpu_width = 0;
recording_gpu_height = 0;
recording_width = 0;
recording_height = 0;
}
#endif
/**
* gfx_ctx_init_first:
* @backend : Recording backend handle.
* @data : Recording data handle.
* @params : Recording info parameters.
*
* Finds first suitable recording context driver and initializes.
*
* Returns: true (1) if successful, otherwise false (0).
**/
static bool record_driver_init_first(
const record_driver_t **backend, void **data,
const struct record_params *params)
{
unsigned i;
for (i = 0; record_drivers[i]; i++)
{
void *handle = record_drivers[i]->init(params);
if (!handle)
continue;
*backend = record_drivers[i];
*data = handle;
return true;
}
return false;
}
static void recording_dump_frame(const void *data, unsigned width,
unsigned height, size_t pitch, bool is_idle)
{
struct record_video_data ffemu_data;
ffemu_data.data = data;
ffemu_data.width = width;
ffemu_data.height = height;
ffemu_data.pitch = (int)pitch;
ffemu_data.is_dupe = false;
if (video_driver_record_gpu_buffer != NULL)
{
struct video_viewport vp;
vp.x = 0;
vp.y = 0;
vp.width = 0;
vp.height = 0;
vp.full_width = 0;
vp.full_height = 0;
video_driver_get_viewport_info(&vp);
if (!vp.width || !vp.height)
{
RARCH_WARN("[recording] %s \n",
msg_hash_to_str(MSG_VIEWPORT_SIZE_CALCULATION_FAILED));
command_event(CMD_EVENT_GPU_RECORD_DEINIT, NULL);
recording_dump_frame(data, width, height, pitch, is_idle);
return;
}
/* User has resized. We kinda have a problem now. */
if ( vp.width != recording_gpu_width ||
vp.height != recording_gpu_height)
{
RARCH_WARN("[recording] %s\n", msg_hash_to_str(MSG_RECORDING_TERMINATED_DUE_TO_RESIZE));
runloop_msg_queue_push(
msg_hash_to_str(MSG_RECORDING_TERMINATED_DUE_TO_RESIZE),
1, 180, true,
NULL, MESSAGE_QUEUE_ICON_DEFAULT, MESSAGE_QUEUE_CATEGORY_INFO);
command_event(CMD_EVENT_RECORD_DEINIT, NULL);
return;
}
/* Big bottleneck.
* Since we might need to do read-backs asynchronously,
* it might take 3-4 times before this returns true. */
if (!video_driver_read_viewport(video_driver_record_gpu_buffer, is_idle))
return;
ffemu_data.pitch = (int)(recording_gpu_width * 3);
ffemu_data.width = (unsigned)recording_gpu_width;
ffemu_data.height = (unsigned)recording_gpu_height;
ffemu_data.data = video_driver_record_gpu_buffer + (ffemu_data.height - 1) * ffemu_data.pitch;
ffemu_data.pitch = -ffemu_data.pitch;
}
else
ffemu_data.is_dupe = !data;
recording_driver->push_video(recording_data, &ffemu_data);
}
bool recording_deinit(void)
{
if (!recording_data || !recording_driver)
return false;
if (recording_driver->finalize)
recording_driver->finalize(recording_data);
if (recording_driver->free)
recording_driver->free(recording_data);
recording_data = NULL;
recording_driver = NULL;
command_event(CMD_EVENT_GPU_RECORD_DEINIT, NULL);
return true;
}
bool recording_is_enabled(void)
{
return recording_enable;
}
void recording_set_state(bool state)
{
recording_enable = state;
}
bool streaming_is_enabled(void)
{
return streaming_enable;
}
void streaming_set_state(bool state)
{
streaming_enable = state;
}
/**
* recording_init:
*
* Initializes recording.
*
* Returns: true (1) if successful, otherwise false (0).
**/
bool recording_init(void)
{
char output[PATH_MAX_LENGTH];
char buf[PATH_MAX_LENGTH];
struct record_params params = {0};
struct retro_system_av_info *av_info = video_viewport_get_system_av_info();
settings_t *settings = config_get_ptr();
global_t *global = &g_extern;
if (!recording_enable)
return false;
output[0] = '\0';
if (rarch_ctl(RARCH_CTL_IS_DUMMY_CORE, NULL))
{
RARCH_WARN("[recording] %s\n",
msg_hash_to_str(MSG_USING_LIBRETRO_DUMMY_CORE_RECORDING_SKIPPED));
return false;
}
if (!settings->bools.video_gpu_record
&& video_driver_is_hw_context())
{
RARCH_WARN("[recording] %s.\n",
msg_hash_to_str(MSG_HW_RENDERED_MUST_USE_POSTSHADED_RECORDING));
return false;
}
RARCH_LOG("[recording] %s: FPS: %.4f, Sample rate: %.4f\n",
msg_hash_to_str(MSG_CUSTOM_TIMING_GIVEN),
(float)av_info->timing.fps,
(float)av_info->timing.sample_rate);
if (!string_is_empty(global->record.path))
strlcpy(output, global->record.path, sizeof(output));
else
{
if(streaming_is_enabled())
if (!string_is_empty(settings->paths.path_stream_url))
strlcpy(output, settings->paths.path_stream_url, sizeof(output));
else
/* Fallback, stream locally to 127.0.0.1 */
snprintf(output, sizeof(output), "udp://127.0.0.1:%u", settings->uints.video_stream_port);
else
{
const char *game_name = path_basename(path_get(RARCH_PATH_BASENAME));
if (settings->uints.video_record_quality < RECORD_CONFIG_TYPE_RECORDING_WEBM_FAST)
{
fill_str_dated_filename(buf, game_name,
"mkv", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
else if (settings->uints.video_record_quality >= RECORD_CONFIG_TYPE_RECORDING_WEBM_FAST && settings->uints.video_record_quality < RECORD_CONFIG_TYPE_RECORDING_GIF)
{
fill_str_dated_filename(buf, game_name,
"webm", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
else if (settings->uints.video_record_quality >= RECORD_CONFIG_TYPE_RECORDING_GIF && settings->uints.video_record_quality < RECORD_CONFIG_TYPE_RECORDING_APNG)
{
fill_str_dated_filename(buf, game_name,
"gif", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
else
{
fill_str_dated_filename(buf, game_name,
"png", sizeof(buf));
fill_pathname_join(output, global->record.output_dir, buf, sizeof(output));
}
}
}
params.out_width = av_info->geometry.base_width;
params.out_height = av_info->geometry.base_height;
params.fb_width = av_info->geometry.max_width;
params.fb_height = av_info->geometry.max_height;
params.channels = 2;
params.filename = output;
params.fps = av_info->timing.fps;
params.samplerate = av_info->timing.sample_rate;
params.pix_fmt = (video_driver_get_pixel_format() == RETRO_PIXEL_FORMAT_XRGB8888) ?
FFEMU_PIX_ARGB8888 : FFEMU_PIX_RGB565;
params.config = NULL;
if (!string_is_empty(global->record.config))
params.config = global->record.config;
else
{
if (streaming_is_enabled())
{
params.config = settings->paths.path_stream_config;
params.preset = (enum record_config_type)
settings->uints.video_stream_quality;
}
else
{
params.config = settings->paths.path_record_config;
params.preset = (enum record_config_type)
settings->uints.video_record_quality;
}
}
if (video_driver_supports_recording())
{
unsigned gpu_size;
struct video_viewport vp;
vp.x = 0;
vp.y = 0;
vp.width = 0;
vp.height = 0;
vp.full_width = 0;
vp.full_height = 0;
video_driver_get_viewport_info(&vp);
if (!vp.width || !vp.height)
{
RARCH_ERR("[recording] Failed to get viewport information from video driver. "
"Cannot start recording ...\n");
return false;
}
params.out_width = vp.width;
params.out_height = vp.height;
params.fb_width = next_pow2(vp.width);
params.fb_height = next_pow2(vp.height);
if (settings->bools.video_force_aspect &&
(video_driver_get_aspect_ratio() > 0.0f))
params.aspect_ratio = video_driver_get_aspect_ratio();
else
params.aspect_ratio = (float)vp.width / vp.height;
params.pix_fmt = FFEMU_PIX_BGR24;
recording_gpu_width = vp.width;
recording_gpu_height = vp.height;
RARCH_LOG("[recording] %s %u x %u\n", msg_hash_to_str(MSG_DETECTED_VIEWPORT_OF),
vp.width, vp.height);
gpu_size = vp.width * vp.height * 3;
if (!video_driver_gpu_record_init(gpu_size))
return false;
}
else
{
if (recording_width || recording_height)
{
params.out_width = recording_width;
params.out_height = recording_height;
}
if (settings->bools.video_force_aspect &&
(video_driver_get_aspect_ratio() > 0.0f))
params.aspect_ratio = video_driver_get_aspect_ratio();
else
params.aspect_ratio = (float)params.out_width / params.out_height;
if (settings->bools.video_post_filter_record
&& video_driver_frame_filter_alive())
{
unsigned max_width = 0;
unsigned max_height = 0;
params.pix_fmt = FFEMU_PIX_RGB565;
if (video_driver_frame_filter_is_32bit())
params.pix_fmt = FFEMU_PIX_ARGB8888;
rarch_softfilter_get_max_output_size(
video_driver_frame_filter_get_ptr(),
&max_width, &max_height);
params.fb_width = next_pow2(max_width);
params.fb_height = next_pow2(max_height);
}
}
RARCH_LOG("[recording] %s %s @ %ux%u. (FB size: %ux%u pix_fmt: %u)\n",
msg_hash_to_str(MSG_RECORDING_TO),
output,
params.out_width, params.out_height,
params.fb_width, params.fb_height,
(unsigned)params.pix_fmt);
if (!record_driver_init_first(&recording_driver, &recording_data, &params))
{
RARCH_ERR("[recording] %s\n", msg_hash_to_str(MSG_FAILED_TO_START_RECORDING));
command_event(CMD_EVENT_GPU_RECORD_DEINIT, NULL);
return false;
}
return true;
}
void *recording_driver_get_data_ptr(void)
{
return recording_data;
}
void recording_driver_update_streaming_url(void)
{
settings_t *settings = config_get_ptr();
const char* youtube_url = "rtmp://a.rtmp.youtube.com/live2/";
const char* twitch_url = "rtmp://live.twitch.tv/app/";
if (!settings)
return;
switch (settings->uints.streaming_mode)
{
case STREAMING_MODE_TWITCH:
{
if (!string_is_empty(settings->arrays.twitch_stream_key))
snprintf(settings->paths.path_stream_url, sizeof(settings->paths.path_stream_url),
"%s%s", twitch_url, settings->arrays.twitch_stream_key);
else
{
/* To-Do: Show input box for twitch_stream_key*/
RARCH_LOG("[recording] twitch streaming key empty\n");
}
break;
}
case STREAMING_MODE_YOUTUBE:
{
if (!string_is_empty(settings->arrays.youtube_stream_key))
{
snprintf(settings->paths.path_stream_url, sizeof(settings->paths.path_stream_url),
"%s%s", youtube_url, settings->arrays.youtube_stream_key);
}
else
{
/* To-Do: Show input box for youtube_stream_key*/
RARCH_LOG("[recording] youtube streaming key empty\n");
}
break;
}
case STREAMING_MODE_LOCAL:
/* To-Do: figure out default interface and bind to that instead */
snprintf(settings->paths.path_stream_url, sizeof(settings->paths.path_stream_url),
"udp://%s:%u", "127.0.0.1", settings->uints.video_stream_port);
break;
case STREAMING_MODE_CUSTOM:
default:
/* Do nothing, let the user input the URL */
break;
}
}
/* Audio */
#define AUDIO_BUFFER_FREE_SAMPLES_COUNT (8 * 1024)
@ -1142,9 +1671,15 @@ void audio_driver_sample(int16_t left, int16_t right)
if (audio_driver_data_ptr < audio_driver_chunk_size)
return;
if (recording_data)
recording_push_audio(audio_driver_output_samples_conv_buf,
audio_driver_data_ptr);
if (recording_data && recording_driver && recording_driver->push_audio)
{
struct record_audio_data ffemu_data;
ffemu_data.data = audio_driver_output_samples_conv_buf;
ffemu_data.frames = audio_driver_data_ptr / 2;
recording_driver->push_audio(recording_data, &ffemu_data);
}
if (!(runloop_paused ||
!audio_driver_active ||
@ -1171,14 +1706,28 @@ static void audio_driver_menu_sample(void)
while (sample_count > 1024)
{
if (recording_data)
recording_push_audio(samples_buf, 1024);
if (recording_data && recording_driver && recording_driver->push_audio)
{
struct record_audio_data ffemu_data;
ffemu_data.data = samples_buf;
ffemu_data.frames = 1024 / 2;
recording_driver->push_audio(recording_data, &ffemu_data);
}
if (check_flush)
audio_driver_flush(samples_buf, 1024, runloop_slowmotion);
sample_count -= 1024;
}
if (recording_data)
recording_push_audio(samples_buf, sample_count);
if (recording_data && recording_driver && recording_driver->push_audio)
{
struct record_audio_data ffemu_data;
ffemu_data.data = samples_buf;
ffemu_data.frames = sample_count / 2;
recording_driver->push_audio(recording_data, &ffemu_data);
}
if (check_flush)
audio_driver_flush(samples_buf, sample_count, runloop_slowmotion);
}
@ -1201,8 +1750,15 @@ size_t audio_driver_sample_batch(const int16_t *data, size_t frames)
if (audio_suspended)
return frames;
if (recording_data)
recording_push_audio(data, frames << 1);
if (recording_data && recording_driver && recording_driver->push_audio)
{
struct record_audio_data ffemu_data;
ffemu_data.data = data;
ffemu_data.frames = (frames << 1) / 2;
recording_driver->push_audio(recording_data, &ffemu_data);
}
if (!(
runloop_paused ||
@ -2003,10 +2559,15 @@ static void audio_driver_unset_callback(void)
void audio_driver_frame_is_reverse(void)
{
/* We just rewound. Flush rewind audio buffer. */
if (recording_data)
recording_push_audio(
audio_driver_rewind_buf + audio_driver_rewind_ptr,
audio_driver_rewind_size - audio_driver_rewind_ptr);
if (recording_data && recording_driver && recording_driver->push_audio)
{
struct record_audio_data ffemu_data;
ffemu_data.data = audio_driver_rewind_buf + audio_driver_rewind_ptr;
ffemu_data.frames = (audio_driver_rewind_size - audio_driver_rewind_ptr) / 2;
recording_driver->push_audio(recording_data, &ffemu_data);
}
if (!(
runloop_paused ||
@ -2255,7 +2816,6 @@ static bool video_driver_cache_context = false;
/* Set to true by driver if context caching succeeded. */
static bool video_driver_cache_context_ack = false;
static uint8_t *video_driver_record_gpu_buffer = NULL;
#ifdef HAVE_THREADS
static slock_t *display_lock = NULL;
@ -3471,7 +4031,7 @@ void video_driver_set_pixel_format(enum retro_pixel_format fmt)
**/
bool video_driver_cached_frame(void)
{
void *recording = recording_driver_get_data_ptr();
void *recording = recording_data;
/* Cannot allow recording when pushing duped frames. */
recording_data = NULL;
@ -4173,14 +4733,6 @@ bool video_driver_is_active(void)
return video_driver_active;
}
void video_driver_get_record_status(
bool *has_gpu_record,
uint8_t **gpu_buf)
{
*gpu_buf = video_driver_record_gpu_buffer;
*has_gpu_record = video_driver_record_gpu_buffer != NULL;
}
bool video_driver_gpu_record_init(unsigned size)
{
video_driver_record_gpu_buffer = (uint8_t*)malloc(size);
@ -4472,6 +5024,7 @@ void video_driver_frame(const void *data, unsigned width,
|| !data
|| video_driver_record_gpu_buffer
) && recording_data
&& recording_driver && recording_driver->push_video
)
recording_dump_frame(data, width, height,
pitch, video_info.runloop_is_idle);
@ -4491,7 +5044,8 @@ void video_driver_frame(const void *data, unsigned width,
video_driver_state_buffer, output_pitch,
data, width, height, pitch);
if (video_info.post_filter_record && recording_data)
if (video_info.post_filter_record && recording_data
&& recording_driver && recording_driver->push_video)
recording_dump_frame(video_driver_state_buffer,
output_width, output_height, output_pitch,
video_info.runloop_is_idle);
@ -6526,7 +7080,7 @@ static bool driver_update_system_av_info(const struct retro_system_av_info *info
/* Cannot continue recording with different parameters.
* Take the easiest route out and just restart the recording. */
if (recording_driver_get_data_ptr())
if (recording_data)
{
runloop_msg_queue_push(
msg_hash_to_str(MSG_RESTARTING_RECORDING_DUE_TO_DRIVER_REINIT),
@ -7497,7 +8051,7 @@ static void retroarch_parse_input_and_config(int argc, char *argv[])
case 'r':
strlcpy(global->record.path, optarg,
sizeof(global->record.path));
if (recording_is_enabled())
if (recording_enable)
recording_set_state(true);
break;
@ -7683,21 +8237,13 @@ static void retroarch_parse_input_and_config(int argc, char *argv[])
break;
case RA_OPT_SIZE:
if (sscanf(optarg, "%ux%u",
&recording_width,
&recording_height) != 2)
{
unsigned recording_width = 0;
unsigned recording_height = 0;
recording_driver_get_size(&recording_width,
&recording_height);
if (sscanf(optarg, "%ux%u",
&recording_width,
&recording_height) != 2)
{
RARCH_ERR("Wrong format for --size.\n");
retroarch_print_help(argv[0]);
retroarch_fail(1, "retroarch_parse_input()");
}
RARCH_ERR("Wrong format for --size.\n");
retroarch_print_help(argv[0]);
retroarch_fail(1, "retroarch_parse_input()");
}
break;

View File

@ -711,6 +711,158 @@ extern audio_driver_t audio_switch_thread;
extern audio_driver_t audio_rwebaudio;
extern audio_driver_t audio_null;
/* Recording */
enum ffemu_pix_format
{
FFEMU_PIX_RGB565 = 0,
FFEMU_PIX_BGR24,
FFEMU_PIX_ARGB8888
};
enum streaming_mode
{
STREAMING_MODE_TWITCH = 0,
STREAMING_MODE_YOUTUBE,
STREAMING_MODE_LOCAL,
STREAMING_MODE_CUSTOM
};
enum record_config_type
{
RECORD_CONFIG_TYPE_RECORDING_CUSTOM = 0,
RECORD_CONFIG_TYPE_RECORDING_LOW_QUALITY,
RECORD_CONFIG_TYPE_RECORDING_MED_QUALITY,
RECORD_CONFIG_TYPE_RECORDING_HIGH_QUALITY,
RECORD_CONFIG_TYPE_RECORDING_LOSSLESS_QUALITY,
RECORD_CONFIG_TYPE_RECORDING_WEBM_FAST,
RECORD_CONFIG_TYPE_RECORDING_WEBM_HIGH_QUALITY,
RECORD_CONFIG_TYPE_RECORDING_GIF,
RECORD_CONFIG_TYPE_RECORDING_APNG,
RECORD_CONFIG_TYPE_STREAMING_CUSTOM,
RECORD_CONFIG_TYPE_STREAMING_LOW_QUALITY,
RECORD_CONFIG_TYPE_STREAMING_MED_QUALITY,
RECORD_CONFIG_TYPE_STREAMING_HIGH_QUALITY,
RECORD_CONFIG_TYPE_STREAMING_NETPLAY
};
/* Parameters passed to ffemu_new() */
struct record_params
{
/* Framerate per second of input video. */
double fps;
/* Sample rate of input audio. */
double samplerate;
/* Desired output resolution. */
unsigned out_width;
unsigned out_height;
/* Total size of framebuffer used in input. */
unsigned fb_width;
unsigned fb_height;
/* Aspect ratio of input video. Parameters are passed to the muxer,
* the video itself is not scaled.
*/
float aspect_ratio;
/* Audio channels. */
unsigned channels;
enum record_config_type preset;
/* Input pixel format. */
enum ffemu_pix_format pix_fmt;
/* Filename to dump to. */
const char *filename;
/* Path to config. Optional. */
const char *config;
};
struct record_video_data
{
const void *data;
unsigned width;
unsigned height;
int pitch;
bool is_dupe;
};
struct record_audio_data
{
const void *data;
size_t frames;
};
typedef struct record_driver
{
void *(*init)(const struct record_params *params);
void (*free)(void *data);
bool (*push_video)(void *data, const struct record_video_data *video_data);
bool (*push_audio)(void *data, const struct record_audio_data *audio_data);
bool (*finalize)(void *data);
const char *ident;
} record_driver_t;
extern const record_driver_t record_ffmpeg;
extern const record_driver_t record_null;
/**
* config_get_record_driver_options:
*
* Get an enumerated list of all record driver names, separated by '|'.
*
* Returns: string listing of all record driver names, separated by '|'.
**/
const char* config_get_record_driver_options(void);
/**
* record_driver_find_handle:
* @idx : index of driver to get handle to.
*
* Returns: handle to record driver at index. Can be NULL
* if nothing found.
**/
const void *record_driver_find_handle(int idx);
/**
* record_driver_find_ident:
* @idx : index of driver to get handle to.
*
* Returns: Human-readable identifier of record driver at index. Can be NULL
* if nothing found.
**/
const char *record_driver_find_ident(int idx);
bool recording_deinit(void);
/**
* recording_init:
*
* Initializes recording.
*
* Returns: true (1) if successful, otherwise false (0).
**/
bool recording_init(void);
bool recording_is_enabled(void);
void recording_set_state(bool state);
void streaming_set_state(bool state);
void *recording_driver_get_data_ptr(void);
bool recording_is_enabled(void);
bool streaming_is_enabled(void);
void recording_driver_update_streaming_url(void);
/* Video */
#ifdef HAVE_OVERLAY
@ -1787,10 +1939,6 @@ void video_driver_reinit(void);
void video_driver_get_window_title(char *buf, unsigned len);
void video_driver_get_record_status(
bool *has_gpu_record,
uint8_t **gpu_buf);
bool *video_driver_get_threaded(void);
void video_driver_set_threaded(bool val);