diff --git a/cores/libretro-video-processor/LICENSE b/cores/libretro-video-processor/LICENSE new file mode 100644 index 0000000000..c3bf9d9079 --- /dev/null +++ b/cores/libretro-video-processor/LICENSE @@ -0,0 +1,23 @@ +Copyright (c) 2016 Jared McNeill +All rights reserved. + +Redistribution and use in source and binary forms, with or without +modification, are permitted provided that the following conditions are met: + +* Redistributions of source code must retain the above copyright notice, this + list of conditions and the following disclaimer. + +* Redistributions in binary form must reproduce the above copyright notice, + this list of conditions and the following disclaimer in the documentation + and/or other materials provided with the distribution. + +THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" +AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE +IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE +DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE +FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL +DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR +SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER +CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, +OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE +OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. diff --git a/cores/libretro-video-processor/Makefile b/cores/libretro-video-processor/Makefile new file mode 100644 index 0000000000..6323f9a53d --- /dev/null +++ b/cores/libretro-video-processor/Makefile @@ -0,0 +1,147 @@ +STATIC_LINKING := 0 +AR := ar + +ifeq ($(platform),) +platform = unix +ifeq ($(shell uname -a),) + platform = win +else ifneq ($(findstring MINGW,$(shell uname -a)),) + platform = win +else ifneq ($(findstring Darwin,$(shell uname -a)),) + platform = osx +else ifneq ($(findstring win,$(shell uname -a)),) + platform = win +endif +endif + +# system platform +system_platform = unix +ifeq ($(shell uname -a),) + EXE_EXT = .exe + system_platform = win +else ifneq ($(findstring Darwin,$(shell uname -a)),) + system_platform = osx + arch = intel +ifeq ($(shell uname -p),powerpc) + arch = ppc +endif +else ifneq ($(findstring MINGW,$(shell uname -a)),) + system_platform = win +endif + +TARGET_NAME := video_processor +LIBV4L2 = -lv4l2 + +ifneq ($(findstring Linux,$(shell uname -a)),) + CFLAGS += -DHAVE_UDEV + LIBUDEV = -ludev + CFLAGS += -DHAVE_ALSA + LIBASOUND = -lasound +endif + +ifeq ($(ARCHFLAGS),) +ifeq ($(archs),ppc) + ARCHFLAGS = -arch ppc -arch ppc64 +else + ARCHFLAGS = -arch i386 -arch x86_64 +endif +endif + +ifeq ($(platform), osx) +ifndef ($(NOUNIVERSAL)) + CFLAGS += $(ARCHFLAGS) + LFLAGS += $(ARCHFLAGS) +endif +endif + +ifeq ($(STATIC_LINKING), 1) +EXT := a +endif + +ifeq ($(platform), unix) + EXT ?= so + TARGET := $(TARGET_NAME)_libretro.$(EXT) + fpic := -fPIC + SHARED := -shared -Wl,--version-script=link.T -Wl,--no-undefined +else ifeq ($(platform), linux-portable) + TARGET := $(TARGET_NAME)_libretro.$(EXT) + fpic := -fPIC -nostdlib + SHARED := -shared -Wl,--version-script=link.T +else ifneq (,$(findstring osx,$(platform))) + TARGET := $(TARGET_NAME)_libretro.dylib + fpic := -fPIC + SHARED := -dynamiclib +else ifneq (,$(findstring ios,$(platform))) + TARGET := $(TARGET_NAME)_libretro_ios.dylib + fpic := -fPIC + SHARED := -dynamiclib + +ifeq ($(IOSSDK),) + IOSSDK := $(shell xcodebuild -version -sdk iphoneos Path) +endif + + DEFINES := -DIOS + CC = cc -arch armv7 -isysroot $(IOSSDK) +ifeq ($(platform),ios9) +CC += -miphoneos-version-min=8.0 +CFLAGS += -miphoneos-version-min=8.0 +else +CC += -miphoneos-version-min=5.0 +CFLAGS += -miphoneos-version-min=5.0 +endif +else ifneq (,$(findstring qnx,$(platform))) + TARGET := $(TARGET_NAME)_libretro_qnx.so + fpic := -fPIC + SHARED := -shared -Wl,--version-script=link.T -Wl,--no-undefined +else ifeq ($(platform), emscripten) + TARGET := $(TARGET_NAME)_libretro_emscripten.bc + fpic := -fPIC + SHARED := -shared -Wl,--version-script=link.T -Wl,--no-undefined +else ifeq ($(platform), vita) + TARGET := $(TARGET_NAME)_vita.a + CC = arm-vita-eabi-gcc + AR = arm-vita-eabi-ar + CFLAGS += -Wl,-q -Wall -O3 + STATIC_LINKING = 1 +else + CC = gcc + TARGET := $(TARGET_NAME)_libretro.dll + SHARED := -shared -static-libgcc -static-libstdc++ -s -Wl,--version-script=link.T -Wl,--no-undefined +endif + +LDFLAGS += $(LIBV4L2) $(LIBASOUND) $(LIBUDEV) + +ifeq ($(DEBUG), 1) + CFLAGS += -O0 -g +else + CFLAGS += -O3 +endif + +OBJECTS := video_processor_v4l2.o +CFLAGS += -Wall -pedantic $(fpic) + +ifneq (,$(findstring qnx,$(platform))) +CFLAGS += -Wc,-std=c99 +else +CFLAGS += -std=gnu99 +endif + +CFLAGS += -I../../libretro-common/include + +all: $(TARGET) + +$(TARGET): $(OBJECTS) +ifeq ($(STATIC_LINKING), 1) + $(AR) rcs $@ $(OBJECTS) +else + $(CC) $(fpic) $(SHARED) $(INCLUDES) -o $@ $(OBJECTS) $(LDFLAGS) +endif + +%.o: %.c + $(CC) $(CFLAGS) $(fpic) -c -o $@ $< + +clean: + rm -f $(OBJECTS) $(TARGET) + +.PHONY: clean + diff --git a/cores/libretro-video-processor/README.md b/cores/libretro-video-processor/README.md new file mode 100644 index 0000000000..2972a3c5c6 --- /dev/null +++ b/cores/libretro-video-processor/README.md @@ -0,0 +1,16 @@ +# libretro-video-processor +Libretro core for V4L2 capture devices + +The basic idea is this -- plug your legacy console into a capture device and use RetroArch to upscale it and apply shaders to taste. + + +## Raspberry Pi specific config + +Add to /boot/config.txt: +``` +gpu_mem_256=112 +gpu_mem_512=368 +cma_lwm=16 +cma_hwm=32 +cma_offline_start=16 +``` diff --git a/cores/libretro-video-processor/link.T b/cores/libretro-video-processor/link.T new file mode 100644 index 0000000000..b0c262db9e --- /dev/null +++ b/cores/libretro-video-processor/link.T @@ -0,0 +1,5 @@ +{ + global: retro_*; + local: *; +}; + diff --git a/cores/libretro-video-processor/video_processor_v4l2.c b/cores/libretro-video-processor/video_processor_v4l2.c new file mode 100644 index 0000000000..658052e10e --- /dev/null +++ b/cores/libretro-video-processor/video_processor_v4l2.c @@ -0,0 +1,698 @@ +/*- + * Copyright (c) 2016 Jared McNeill + * All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions are met: + * + * * Redistributions of source code must retain the above copyright notice, this + * list of conditions and the following disclaimer. + * + * * Redistributions in binary form must reproduce the above copyright notice, + * this list of conditions and the following disclaimer in the documentation + * and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE + * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE + * FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL + * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR + * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER + * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE + * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + */ + +#define LIBRARY_NAME "V4L2" +#define LIBRARY_VERSION "0.0.2" +#define VIDEO_BUFFERS 2 +#define AUDIO_SAMPLE_RATE 48000 +#define AUDIO_BUFSIZE 64 +#define ENVVAR_BUFLEN 1024 + +#include "libretro.h" + +#include +#include +#include +#include +#include +#include + +#include +#include + +#ifdef HAVE_ALSA +#include +#endif + +#ifdef HAVE_UDEV +#include +#endif + +struct video_buffer { + void *start; + size_t len; +}; + +/* + * Video capture state + */ +static int video_fd = -1; +static struct v4l2_format video_format; +static struct v4l2_standard video_standard; +static struct video_buffer video_buffer[VIDEO_BUFFERS]; +static size_t video_nbuffers; +static uint16_t *conv_data; + +/* + * Audio capture state + */ +#ifdef HAVE_ALSA +static snd_pcm_t *audio_handle; +#endif + +/* + * Libretro API callbacks + */ +static retro_environment_t environment_cb; +static retro_video_refresh_t video_refresh_cb; +static retro_audio_sample_t audio_sample_cb; +static retro_audio_sample_batch_t audio_sample_batch_cb; +static retro_input_poll_t input_poll_cb; +static retro_input_state_t input_state_cb; + +#ifdef HAVE_ALSA +static void +audio_callback(void) +{ + int16_t audio_data[128]; + int i, frame; + + if (audio_handle) { + const int frames = snd_pcm_readi(audio_handle, audio_data, sizeof(audio_data) / 4); + for (frame = 0, i = 0; frame < frames; frame++, i += 2) + audio_sample_cb(audio_data[i+0], audio_data[i+1]); + } +} + +static void +audio_set_state(bool enable) +{ +} +#endif + +static void +appendstr(char *dst, const char *src, size_t dstsize) +{ + size_t resid; + + resid = dstsize - (strlen(dst) + 1); + if (resid == 0) + return; + strncat(dst, src, resid); +} + +static void +enumerate_video_devices(char *buf, size_t buflen) +{ + memset(buf, 0, buflen); + + appendstr(buf, "Video capture device; ", buflen); + +#ifdef HAVE_UDEV + /* Get a list of devices matching the "video4linux" subsystem from udev */ + struct udev *udev; + struct udev_device *dev; + struct udev_enumerate *enumerate; + struct udev_list_entry *devices, *dev_list_entry; + const char *path, *name; + int ndevs; + + udev = udev_new(); + if (!udev) { + printf("Cannot create udev context\n"); + return; + } + + enumerate = udev_enumerate_new(udev); + if (!enumerate) { + printf("Cannot create enumerate context\n"); + udev_unref(udev); + return; + } + + udev_enumerate_add_match_subsystem(enumerate, "video4linux"); + udev_enumerate_scan_devices(enumerate); + + devices = udev_enumerate_get_list_entry(enumerate); + if (!devices) { + printf("Cannot get video device list\n"); + udev_enumerate_unref(enumerate); + udev_unref(udev); + return; + } + + ndevs = 0; + udev_list_entry_foreach(dev_list_entry, devices) { + path = udev_list_entry_get_name(dev_list_entry); + dev = udev_device_new_from_syspath(udev, path); + name = udev_device_get_devnode(dev); + + if (strncmp(name, "/dev/video", strlen("/dev/video")) == 0) { + if (ndevs > 0) + appendstr(buf, "|", buflen); + appendstr(buf, name, buflen); + ndevs++; + } + + udev_device_unref(dev); + } + + udev_enumerate_unref(enumerate); + udev_unref(udev); +#else + /* Just return a few options. We'll fail later if the device is not found. */ + appendstr(buf, "/dev/video0|/dev/video1|/dev/video2|/dev/video3", buflen); +#endif +} + +static void +enumerate_audio_devices(char *buf, size_t buflen) +{ + memset(buf, 0, buflen); + + appendstr(buf, "Audio capture device; ", buflen); + +#ifdef HAVE_ALSA + void **hints, **n; + char *ioid, *name; + int ndevs; + + if (snd_device_name_hint(-1, "pcm", &hints) < 0) + return; + + ndevs = 0; + for (n = hints; *n; n++) { + name = snd_device_name_get_hint(*n, "NAME"); + ioid = snd_device_name_get_hint(*n, "IOID"); + if ((ioid == NULL || strcmp(ioid, "Input") == 0) && + (strncmp(name, "hw:", strlen("hw:")) == 0 || strncmp(name, "default:", strlen("default:")) == 0)) { + if (ndevs > 0) + appendstr(buf, "|", buflen); + appendstr(buf, name, buflen); + ++ndevs; + } + free(name); + free(ioid); + } + + snd_device_name_free_hint(hints); +#endif +} + +RETRO_API void +retro_set_environment(retro_environment_t cb) +{ + char video_devices[ENVVAR_BUFLEN]; + char audio_devices[ENVVAR_BUFLEN]; + + environment_cb = cb; + +#ifdef HAVE_ALSA + struct retro_audio_callback audio_cb; + audio_cb.callback = audio_callback; + audio_cb.set_state = audio_set_state; + environment_cb(RETRO_ENVIRONMENT_SET_AUDIO_CALLBACK, &audio_cb); +#endif + + enumerate_video_devices(video_devices, sizeof(video_devices)); + enumerate_audio_devices(audio_devices, sizeof(audio_devices)); + + struct retro_variable envvars[] = { + { "v4l2_videodev", video_devices }, + { "v4l2_audiodev", audio_devices }, + { NULL, NULL } + }; + + environment_cb(RETRO_ENVIRONMENT_SET_VARIABLES, envvars); +} + +RETRO_API void +retro_set_video_refresh(retro_video_refresh_t cb) +{ + video_refresh_cb = cb; +} + +RETRO_API void +retro_set_audio_sample(retro_audio_sample_t cb) +{ + audio_sample_cb = cb; +} + +RETRO_API void +retro_set_audio_sample_batch(retro_audio_sample_batch_t cb) +{ + audio_sample_batch_cb = cb; +} + +RETRO_API void +retro_set_input_poll(retro_input_poll_t cb) +{ + input_poll_cb = cb; +} + +RETRO_API void +retro_set_input_state(retro_input_state_t cb) +{ + input_state_cb = cb; +} + +RETRO_API void +retro_init(void) +{ +} + +static bool +open_devices(void) +{ + struct retro_variable videodev = { "v4l2_videodev", NULL }; + struct retro_variable audiodev = { "v4l2_audiodev", NULL }; + struct v4l2_capability caps; + int error; + + /* Get the video and audio capture device names from the environment */ + environment_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &videodev); + environment_cb(RETRO_ENVIRONMENT_GET_VARIABLE, &audiodev); + + /* Video device is required */ + if (videodev.value == NULL) { + printf("v4l2_videodev not defined\n"); + return false; + } + + /* Open the V4L2 device */ + video_fd = v4l2_open(videodev.value, O_RDWR, 0); + if (video_fd == -1) { + printf("Couldn't open %s: %s\n", videodev.value, strerror(errno)); + return false; + } + + /* Query V4L2 device capabilities */ + error = v4l2_ioctl(video_fd, VIDIOC_QUERYCAP, &caps); + if (error != 0) { + printf("VIDIOC_QUERYCAP failed: %s\n", strerror(errno)); + v4l2_close(video_fd); + return false; + } + + printf("%s:\n", videodev.value); + printf(" Driver: %s\n", caps.driver); + printf(" Card: %s\n", caps.card); + printf(" Bus Info: %s\n", caps.bus_info); + printf(" Version: %u.%u.%u\n", (caps.version >> 16) & 0xff, (caps.version >> 8) & 0xff, caps.version & 0xff); + +#ifdef HAVE_ALSA + if (audiodev.value) { + snd_pcm_hw_params_t *hw_params; + unsigned int rate; + + /* + * Open the audio capture device and configure it for 48kHz, 16-bit stereo + */ + error = snd_pcm_open(&audio_handle, audiodev.value, SND_PCM_STREAM_CAPTURE, 0); + if (error < 0) { + printf("Couldn't open %s: %s\n", audiodev.value, snd_strerror(error)); + return false; + } + + error = snd_pcm_hw_params_malloc(&hw_params); + if (error) { + printf("Couldn't allocate hw param structure: %s\n", snd_strerror(error)); + return false; + } + error = snd_pcm_hw_params_any(audio_handle, hw_params); + if (error) { + printf("Couldn't initialize hw param structure: %s\n", snd_strerror(error)); + return false; + } + error = snd_pcm_hw_params_set_access(audio_handle, hw_params, SND_PCM_ACCESS_RW_INTERLEAVED); + if (error) { + printf("Couldn't set hw param access type: %s\n", snd_strerror(error)); + return false; + } + error = snd_pcm_hw_params_set_format(audio_handle, hw_params, SND_PCM_FORMAT_S16_LE); + if (error) { + printf("Couldn't set hw param format to SND_PCM_FORMAT_S16_LE: %s\n", snd_strerror(error)); + return false; + } + rate = AUDIO_SAMPLE_RATE; + error = snd_pcm_hw_params_set_rate_near(audio_handle, hw_params, &rate, 0); + if (error) { + printf("Couldn't set hw param sample rate to %u: %s\n", rate, snd_strerror(error)); + return false; + } + if (rate != AUDIO_SAMPLE_RATE) { + printf("Hardware doesn't support sample rate %u (returned %u)\n", AUDIO_SAMPLE_RATE, rate); + return false; + } + error = snd_pcm_hw_params_set_channels(audio_handle, hw_params, 2); + if (error) { + printf("Couldn't set hw param channels to 2: %s\n", snd_strerror(error)); + return false; + } + error = snd_pcm_hw_params(audio_handle, hw_params); + if (error) { + printf("Couldn't set hw params: %s\n", snd_strerror(error)); + return false; + } + snd_pcm_hw_params_free(hw_params); + + error = snd_pcm_prepare(audio_handle); + if (error) { + printf("Couldn't prepare audio interface for use: %s\n", snd_strerror(error)); + return false; + } + + printf("Using ALSA device %s for audio input\n", audiodev.value); + } +#endif + + return true; +} + +static void +close_devices(void) +{ +#ifdef HAVE_ALSA + if (audio_handle) { + snd_pcm_close(audio_handle); + audio_handle = NULL; + } +#endif + + if (video_fd != -1) { + v4l2_close(video_fd); + video_fd = -1; + } +} + +RETRO_API void +retro_deinit(void) +{ + close_devices(); +} + +RETRO_API unsigned +retro_api_version(void) +{ + return RETRO_API_VERSION; +} + +RETRO_API void +retro_get_system_info(struct retro_system_info *info) +{ + info->library_name = LIBRARY_NAME; + info->library_version = LIBRARY_VERSION; + info->valid_extensions = ""; + info->need_fullpath = true; + info->block_extract = true; +} + +RETRO_API void +retro_get_system_av_info(struct retro_system_av_info *info) +{ + struct v4l2_cropcap cc; + int error; + + /* + * Query the device cropping limits. If available, we can use this to find the capture pixel aspect. + */ + memset(&cc, 0, sizeof(cc)); + cc.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + error = v4l2_ioctl(video_fd, VIDIOC_CROPCAP, &cc); + if (error == 0) { + info->geometry.aspect_ratio = (double)cc.pixelaspect.denominator / (double)cc.pixelaspect.numerator; + } + + info->geometry.base_width = info->geometry.max_width = video_format.fmt.pix.width; + info->geometry.base_height = info->geometry.max_height = video_format.fmt.pix.height; + info->timing.fps = (double)video_standard.frameperiod.denominator / (double)video_standard.frameperiod.numerator; + info->timing.sample_rate = AUDIO_SAMPLE_RATE; + + printf("Resolution %ux%u %f fps\n", info->geometry.base_width, info->geometry.base_height, info->timing.fps); +} + +RETRO_API void +retro_set_controller_port_device(unsigned port, unsigned device) +{ +} + +RETRO_API void +retro_reset(void) +{ + close_devices(); + open_devices(); +} + +RETRO_API void +retro_run(void) +{ + struct v4l2_buffer buf; + uint8_t *src; + uint16_t *dst; + int i, error; + + input_poll_cb(); + + if (video_fd == -1) + return; + + memset(&buf, 0, sizeof(buf)); + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + error = v4l2_ioctl(video_fd, VIDIOC_DQBUF, &buf); + if (error != 0) { + printf("VIDIOC_DQBUF failed: %s\n", strerror(errno)); + video_refresh_cb(NULL, 0, 0, 0); + return; + } + + src = video_buffer[buf.index].start; + dst = conv_data; + + /* RGB24 to RGB565 */ + for (i = 0; i < video_format.fmt.pix.width * video_format.fmt.pix.height; i++, src += 3, dst += 1) { + *dst = ((src[0] >> 3) << 11) | ((src[1] >> 2) << 5) | ((src[2] >> 3) << 0); + } + + error = v4l2_ioctl(video_fd, VIDIOC_QBUF, &buf); + if (error != 0) + printf("VIDIOC_QBUF failed: %s\n", strerror(errno)); + + video_refresh_cb(conv_data, video_format.fmt.pix.width, video_format.fmt.pix.height, video_format.fmt.pix.width * 2); +} + +RETRO_API size_t +retro_serialize_size(void) +{ + return 0; +} + +RETRO_API bool +retro_serialize(void *data, size_t size) +{ + return false; +} + +RETRO_API bool +retro_unserialize(const void *data, size_t size) +{ + return false; +} + +RETRO_API void +retro_cheat_reset(void) +{ +} + +RETRO_API void +retro_cheat_set(unsigned index, bool enabled, const char *code) +{ +} + +RETRO_API bool +retro_load_game(const struct retro_game_info *game) +{ + enum retro_pixel_format pixel_format; + struct v4l2_standard std; + struct v4l2_requestbuffers reqbufs; + struct v4l2_buffer buf; + struct v4l2_format fmt; + enum v4l2_buf_type type; + v4l2_std_id std_id; + uint32_t index; + bool std_found; + int error; + + if (open_devices() == false) { + printf("Couldn't open capture device\n"); + close_devices(); + return false; + } + + memset(&fmt, 0, sizeof(fmt)); + fmt.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + + error = v4l2_ioctl(video_fd, VIDIOC_G_FMT, &fmt); + if (error != 0) { + printf("VIDIOC_G_FMT failed: %s\n", strerror(errno)); + return false; + } + fmt.fmt.pix.pixelformat = V4L2_PIX_FMT_RGB24; + error = v4l2_ioctl(video_fd, VIDIOC_S_FMT, &fmt); + if (error != 0) { + printf("VIDIOC_S_FMT failed: %s\n", strerror(errno)); + return false; + } + + error = v4l2_ioctl(video_fd, VIDIOC_G_STD, &std_id); + if (error != 0) { + printf("VIDIOC_G_STD failed: %s\n", strerror(errno)); + return false; + } + for (index = 0, std_found = false; ; index++) { + memset(&std, 0, sizeof(std)); + std.index = index; + error = v4l2_ioctl(video_fd, VIDIOC_ENUMSTD, &std); + if (error) + break; + if (std.id == std_id) { + video_standard = std; + std_found = true; + } + printf("VIDIOC_ENUMSTD[%u]: %s%s\n", index, std.name, std.id == std_id ? " [*]" : ""); + } + if (!std_found) { + printf("VIDIOC_ENUMSTD did not contain std ID %08x\n", (unsigned)std_id); + return false; + } + + video_format = fmt; + + memset(&reqbufs, 0, sizeof(reqbufs)); + reqbufs.count = VIDEO_BUFFERS; + reqbufs.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + reqbufs.memory = V4L2_MEMORY_MMAP; + + error = v4l2_ioctl(video_fd, VIDIOC_REQBUFS, &reqbufs); + if (error != 0) { + printf("VIDIOC_REQBUFS failed: %s\n", strerror(errno)); + return false; + } + video_nbuffers = reqbufs.count; + + for (index = 0; index < video_nbuffers; index++) { + memset(&buf, 0, sizeof(buf)); + buf.index = index; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + error = v4l2_ioctl(video_fd, VIDIOC_QUERYBUF, &buf); + if (error != 0) { + printf("VIDIOC_QUERYBUF failed for %u: %s\n", index, strerror(errno)); + return false; + } + + video_buffer[index].len = buf.length; + video_buffer[index].start = v4l2_mmap(NULL, buf.length, PROT_READ|PROT_WRITE, MAP_SHARED, video_fd, buf.m.offset); + if (video_buffer[index].start == MAP_FAILED) { + printf("v4l2_mmap failed: %s\n", strerror(errno)); + return false; + } + } + + for (index = 0; index < video_nbuffers; index++) { + memset(&buf, 0, sizeof(buf)); + buf.index = index; + buf.type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + buf.memory = V4L2_MEMORY_MMAP; + + error = v4l2_ioctl(video_fd, VIDIOC_QBUF, &buf); + if (error != 0) { + printf("VIDIOC_QBUF failed for %u: %s\n", index, strerror(errno)); + return false; + } + } + + conv_data = calloc(1, video_format.fmt.pix.width * video_format.fmt.pix.height * 2); + if (conv_data == NULL) { + printf("Cannot allocate conversion buffer\n"); + return false; + } + printf("Allocated %u byte conversion buffer\n", video_format.fmt.pix.width * video_format.fmt.pix.height * 2); + + pixel_format = RETRO_PIXEL_FORMAT_RGB565; + if (!environment_cb(RETRO_ENVIRONMENT_SET_PIXEL_FORMAT, &pixel_format)) { + printf("Cannot set pixel format\n"); + return false; + } + + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + error = v4l2_ioctl(video_fd, VIDIOC_STREAMON, &type); + if (error != 0) { + printf("VIDIOC_STREAMON failed: %s\n", strerror(errno)); + return false; + } + + return true; +} + +RETRO_API void +retro_unload_game(void) +{ + enum v4l2_buf_type type; + uint32_t index; + int error; + + if (video_fd != -1) { + type = V4L2_BUF_TYPE_VIDEO_CAPTURE; + error = v4l2_ioctl(video_fd, VIDIOC_STREAMOFF, &type); + if (error != 0) + printf("VIDIOC_STREAMOFF failed: %s\n", strerror(errno)); + + for (index = 0; index < video_nbuffers; index++) + v4l2_munmap(video_buffer[index].start, video_buffer[index].len); + } + + free(conv_data); + conv_data = NULL; + + close_devices(); +} + +RETRO_API bool +retro_load_game_special(unsigned game_type, const struct retro_game_info *info, size_t num_info) +{ + return false; +} + +RETRO_API unsigned +retro_get_region(void) +{ + return (video_standard.id & V4L2_STD_NTSC) != 0 ? RETRO_REGION_NTSC : RETRO_REGION_PAL; +} + +RETRO_API void * +retro_get_memory_data(unsigned id) +{ + return NULL; +} + +RETRO_API size_t +retro_get_memory_size(unsigned id) +{ + return 0; +}