cc3dsfs/source/usb_is_nitro_capture.cpp
Lorenzooone 7c3cc5fee8
Some checks failed
CD / ${{ matrix.platform.name }} ${{ matrix.config.name }} (map[flags:-DBUILD_SHARED_LIBS=FALSE name:Static], map[flags:-A ARM64 name:Windows VS2022 ARM os:windows-2022]) (push) Has been cancelled
CD / ${{ matrix.platform.name }} ${{ matrix.config.name }} (map[flags:-DBUILD_SHARED_LIBS=FALSE name:Static], map[flags:-A Win32 name:Windows VS2022 Win32 os:windows-2022]) (push) Has been cancelled
CD / ${{ matrix.platform.name }} ${{ matrix.config.name }} (map[flags:-DBUILD_SHARED_LIBS=FALSE name:Static], map[flags:-A x64 name:Windows VS2022 x64 os:windows-2022]) (push) Has been cancelled
CD / ${{ matrix.platform.name }} ${{ matrix.config.name }} (map[flags:-DBUILD_SHARED_LIBS=FALSE name:Static], map[flags:32 name:Linux GCC 32 os:ubuntu-latest]) (push) Has been cancelled
CD / ${{ matrix.platform.name }} ${{ matrix.config.name }} (map[flags:-DBUILD_SHARED_LIBS=FALSE name:Static], map[flags:64 name:Linux GCC x64 os:ubuntu-latest]) (push) Has been cancelled
CD / ${{ matrix.platform.name }} ${{ matrix.config.name }} (map[flags:-DBUILD_SHARED_LIBS=FALSE name:Static], map[flags:arm32 name:Linux GCC ARM 32 os:ubuntu-latest]) (push) Has been cancelled
CD / ${{ matrix.platform.name }} ${{ matrix.config.name }} (map[flags:-DBUILD_SHARED_LIBS=FALSE name:Static], map[flags:arm64 name:Linux GCC ARM 64 os:ubuntu-latest]) (push) Has been cancelled
CD / ${{ matrix.platform.name }} ${{ matrix.config.name }} (map[flags:-DBUILD_SHARED_LIBS=FALSE name:Static], map[name:macOS Apple Silicon os:macos-14]) (push) Has been cancelled
CD / Create Pi Mono Setup (push) Has been cancelled
CD / Publishing (push) Has been cancelled
Add IS Nitro Capture support
2024-10-19 21:47:33 +02:00

568 lines
24 KiB
C++

#include "devicecapture.hpp"
#include "usb_is_nitro.hpp"
#include "usb_is_nitro_capture.hpp"
#include "usb_generic.hpp"
#include <libusb.h>
#include <cstring>
#include <thread>
#include <chrono>
#include <iostream>
// Code based off of Gericom's sample code. Distributed under the MIT License. Copyright (c) 2024 Gericom
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
// IN THE SOFTWARE.
#define SERIAL_NUMBER_SIZE (IS_NITRO_REAL_SERIAL_NUMBER_SIZE + 1)
#define FRAME_BUFFER_SIZE 32
enum usb_capture_status {
USB_CAPTURE_SUCCESS = 0,
USB_CAPTURE_SKIP,
USB_CAPTURE_PIPE_ERROR,
USB_CAPTURE_FRAMEINFO_ERROR,
USB_CAPTURE_ERROR
};
static int drain_frames(libusb_device_handle *handle, int num_frames, int start_frames, CaptureScreensType capture_type, const is_nitro_usb_device* usb_device_desc);
static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int start_frames, CaptureScreensType capture_type, const is_nitro_usb_device* usb_device_desc);
static void is_nitro_capture_capture_main_loop(CaptureData* capture_data);
static void is_nitro_emulator_capture_main_loop(CaptureData* capture_data);
static int StartEmulatorCapture(libusb_device_handle *handle, uint16_t &out_frame_count, float &single_frame_time, CaptureScreensType capture_type, const is_nitro_usb_device* usb_device_desc);
static int StartCaptureCapture(libusb_device_handle *handle, CaptureScreensType capture_type, const is_nitro_usb_device* usb_device_desc);
static std::string get_serial(const is_nitro_usb_device* usb_device_desc, libusb_device_handle *handle, int &curr_serial_extra_id) {
uint8_t data[SERIAL_NUMBER_SIZE];
std::string serial_str = std::to_string(curr_serial_extra_id);
bool conn_success = true;
if(libusb_set_configuration(handle, usb_device_desc->default_config) != LIBUSB_SUCCESS)
conn_success = false;
if(conn_success && libusb_claim_interface(handle, usb_device_desc->default_interface) != LIBUSB_SUCCESS)
conn_success = false;
if((!usb_device_desc->is_capture) && conn_success && EndCapture(handle, false, 0, CAPTURE_SCREENS_BOTH, usb_device_desc) != LIBUSB_SUCCESS)
conn_success = false;
if(conn_success && (GetDeviceSerial(handle, data, usb_device_desc) != LIBUSB_SUCCESS)) {
int ret = 0;
while(ret >= 0)
ret = drain_frames(handle, FRAME_BUFFER_SIZE * 2, 0, CAPTURE_SCREENS_TOP, usb_device_desc);
if((GetDeviceSerial(handle, data, usb_device_desc) != LIBUSB_SUCCESS))
conn_success = false;
}
if(conn_success) {
data[IS_NITRO_REAL_SERIAL_NUMBER_SIZE] = '\0';
serial_str = std::string((const char*)data);
}
else
curr_serial_extra_id += 1;
if(conn_success)
libusb_release_interface(handle, usb_device_desc->default_interface);
return serial_str;
}
static int insert_device(std::vector<CaptureDevice> &devices_list, const is_nitro_usb_device* usb_device_desc, libusb_device *usb_device, libusb_device_descriptor *usb_descriptor, int &curr_serial_extra_id) {
libusb_device_handle *handle = NULL;
if((usb_descriptor->idVendor != usb_device_desc->vid) || (usb_descriptor->idProduct != usb_device_desc->pid))
return LIBUSB_ERROR_NOT_FOUND;
if((usb_descriptor->iManufacturer != usb_device_desc->manufacturer_id) || (usb_descriptor->iProduct != usb_device_desc->product_id))
return LIBUSB_ERROR_NOT_FOUND;
int result = libusb_open(usb_device, &handle);
if((result < 0) || (handle == NULL))
return result;
devices_list.emplace_back(get_serial(usb_device_desc, handle, curr_serial_extra_id), usb_device_desc->name, CAPTURE_CONN_IS_NITRO, (void*)usb_device_desc, false, false, false, WIDTH_DS, HEIGHT_DS + HEIGHT_DS, 0, 0, 0, 0, 0, HEIGHT_DS);
libusb_close(handle);
return result;
}
static libusb_device_handle* usb_find_by_serial_number(const is_nitro_usb_device* usb_device_desc, std::string &serial_number) {
if(!usb_is_initialized())
return NULL;
libusb_device **usb_devices;
int num_devices = libusb_get_device_list(get_usb_ctx(), &usb_devices);
libusb_device_descriptor usb_descriptor{};
libusb_device_handle *final_handle = NULL;
int curr_serial_extra_id = 0;
for(int i = 0; i < num_devices; i++) {
libusb_device_handle *handle = NULL;
uint8_t data[SERIAL_NUMBER_SIZE];
int result = libusb_get_device_descriptor(usb_devices[i], &usb_descriptor);
if(result < 0)
continue;
if((usb_descriptor.idVendor != usb_device_desc->vid) || (usb_descriptor.idProduct != usb_device_desc->pid))
continue;
if((usb_descriptor.iManufacturer != usb_device_desc->manufacturer_id) || (usb_descriptor.iProduct != usb_device_desc->product_id))
continue;
result = libusb_open(usb_devices[i], &handle);
if(result || (handle == NULL))
continue;
std::string device_serial_number = get_serial(usb_device_desc, handle, curr_serial_extra_id);
if(serial_number == device_serial_number) {
final_handle = handle;
break;
}
libusb_close(handle);
}
if(num_devices >= 0)
libusb_free_device_list(usb_devices, 1);
return final_handle;
}
void list_devices_is_nitro(std::vector<CaptureDevice> &devices_list) {
if(!usb_is_initialized())
return;
libusb_device **usb_devices;
int num_devices = libusb_get_device_list(get_usb_ctx(), &usb_devices);
libusb_device_descriptor usb_descriptor{};
int curr_serial_extra_id_is_nitro_emulator = 0;
for(int i = 0; i < num_devices; i++) {
int result = libusb_get_device_descriptor(usb_devices[i], &usb_descriptor);
if(result < 0)
continue;
result = insert_device(devices_list, GetISNitroDesc(IS_NITRO_EMULATOR_COMMON_ID), usb_devices[i], &usb_descriptor, curr_serial_extra_id_is_nitro_emulator);
if(result != LIBUSB_ERROR_NOT_FOUND) {
continue;
}
result = insert_device(devices_list, GetISNitroDesc(IS_NITRO_EMULATOR_RARE_ID), usb_devices[i], &usb_descriptor, curr_serial_extra_id_is_nitro_emulator);
if(result != LIBUSB_ERROR_NOT_FOUND) {
continue;
}
result = insert_device(devices_list, GetISNitroDesc(IS_NITRO_CAPTURE_ID), usb_devices[i], &usb_descriptor, curr_serial_extra_id_is_nitro_emulator);
if(result != LIBUSB_ERROR_NOT_FOUND) {
continue;
}
}
if(num_devices >= 0)
libusb_free_device_list(usb_devices, 1);
}
static void is_nitro_connection_end(libusb_device_handle *dev, const is_nitro_usb_device *device_desc, bool interface_claimed = true) {
if(interface_claimed)
libusb_release_interface(dev, device_desc->default_interface);
libusb_close(dev);
}
bool is_nitro_connect_usb(bool print_failed, CaptureData* capture_data, CaptureDevice* device) {
if(!usb_is_initialized())
return false;
const is_nitro_usb_device* usb_device_info = (const is_nitro_usb_device*)device->descriptor;
libusb_device_handle *dev = usb_find_by_serial_number(usb_device_info, device->serial_number);
if(!dev) {
capture_error_print(true, capture_data, "Device not found");
return false;
}
if(libusb_set_configuration(dev, usb_device_info->default_config) != LIBUSB_SUCCESS) {
capture_error_print(true, capture_data, "Configuration failed");
is_nitro_connection_end(dev, usb_device_info, false);
return false;
}
if(libusb_claim_interface(dev, usb_device_info->default_interface) != LIBUSB_SUCCESS) {
capture_error_print(true, capture_data, "Interface claim failed");
is_nitro_connection_end(dev, usb_device_info, false);
return false;
}
capture_data->handle = (void*)dev;
return true;
}
static uint64_t _is_nitro_emulator_get_video_in_size(CaptureScreensType capture_type) {
if((capture_type == CAPTURE_SCREENS_TOP) || (capture_type == CAPTURE_SCREENS_BOTTOM))
return sizeof(ISNitroEmulatorVideoInputData) / 2;
return sizeof(ISNitroEmulatorVideoInputData);
}
uint64_t usb_is_nitro_emulator_get_video_in_size(CaptureData* capture_data) {
return _is_nitro_emulator_get_video_in_size(capture_data->status.capture_type);
}
static int drain_frames(libusb_device_handle *handle, int num_frames, int start_frames, CaptureScreensType capture_type, const is_nitro_usb_device* usb_device_desc) {
ISNitroEmulatorVideoInputData video_in_buffer;
for (int i = start_frames; i < num_frames; i++) {
int ret = ReadFrame(handle, (uint8_t*)&video_in_buffer, _is_nitro_emulator_get_video_in_size(capture_type), usb_device_desc);
if(ret < 0)
return ret;
}
return LIBUSB_SUCCESS;
}
static int set_capture_mode(libusb_device_handle *handle, CaptureScreensType capture_type, const is_nitro_usb_device* usb_device_desc) {
is_nitro_forward_config_values_screens capture_mode_flag = IS_NITRO_FORWARD_CONFIG_MODE_BOTH;
if(capture_type == CAPTURE_SCREENS_TOP)
capture_mode_flag = IS_NITRO_FORWARD_CONFIG_MODE_TOP;
if(capture_type == CAPTURE_SCREENS_BOTTOM)
capture_mode_flag = IS_NITRO_FORWARD_CONFIG_MODE_BOTTOM;
return UpdateFrameForwardConfig(handle, IS_NITRO_FORWARD_CONFIG_COLOR_RGB24, capture_mode_flag, IS_NITRO_FORWARD_CONFIG_RATE_FULL, usb_device_desc);
}
static int StartEmulatorCapture(libusb_device_handle *handle, uint16_t &out_frame_count, float &single_frame_time, CaptureScreensType capture_type, const is_nitro_usb_device* usb_device_desc) {
int ret = 0;
ret = DisableLca2(handle, usb_device_desc);
if(ret < 0)
return ret;
ret = set_capture_mode(handle, capture_type, usb_device_desc);
if(ret < 0)
return ret;
ret = SetForwardFrameCount(handle, FRAME_BUFFER_SIZE, usb_device_desc);
if(ret < 0)
return ret;
// Reset this in case it's high. At around 0xFFFF, reading from the USB DMA seems to fail...
ret = UpdateFrameForwardEnable(handle, true, true, usb_device_desc);
if(ret < 0)
return ret;
ret = UpdateFrameForwardEnable(handle, true, false, usb_device_desc);
if(ret < 0)
return ret;
// Get to the closest next frame
auto clock_start = std::chrono::high_resolution_clock::now();
uint16_t oldFrameCount;
uint16_t newFrameCount;
ret = GetFrameCounter(handle, &oldFrameCount, usb_device_desc);
if(ret < 0)
return ret;
newFrameCount = oldFrameCount;
while(newFrameCount == oldFrameCount) {
ret = GetFrameCounter(handle, &newFrameCount, usb_device_desc);
if(ret < 0)
return ret;
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_start;
// If too much time has passed, the DS is probably either turned off or sleeping. If so, avoid locking up
if(diff.count() > 0.2)
break;
}
// Get to the next modulo 32 frame.
// We also do this to measure the time that is needed for each frame...
// To do so, a minimum of 4 frames is required (FRAME_BUFFER_SIZE - 1 + 4)
clock_start = std::chrono::high_resolution_clock::now();
ret = GetFrameCounter(handle, &oldFrameCount, usb_device_desc);
if(ret < 0)
return ret;
uint16_t targetFrameCount = (newFrameCount + FRAME_BUFFER_SIZE + 3) & (~(FRAME_BUFFER_SIZE - 1));
while(oldFrameCount != targetFrameCount) {
ret = GetFrameCounter(handle, &oldFrameCount, usb_device_desc);
if(ret < 0)
return ret;
// Placing a sleep of some kind here would be much better...
// Though this is only executed for a small time when first connecting...
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_start;
// If too much time has passed, the DS is probably either turned off or sleeping. If so, avoid locking up
if(diff.count() > 1.0)
break;
}
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_start;
// Sometimes the upper 8 bits aren't updated... Use only the lower 8 bits.
newFrameCount &= 0xFF;
oldFrameCount &= 0xFF;
int frame_diff = ((int)oldFrameCount) - ((int)newFrameCount);
if(frame_diff < 0)
frame_diff += 1 << 8;
out_frame_count = oldFrameCount;
// Determine how much time a single frame takes. We'll use it for sleeps
if(frame_diff == 0)
single_frame_time = 0;
else
single_frame_time = diff.count() / frame_diff;
// Start the actual DMA
if(single_frame_time > 0) {
ret = StartUsbCaptureDma(handle, usb_device_desc);
if(ret < 0)
return ret;
}
return ret;
}
static int StartCaptureCapture(libusb_device_handle *handle, CaptureScreensType capture_type, const is_nitro_usb_device* usb_device_desc) {
int ret = 0;
ret = set_capture_mode(handle, capture_type, usb_device_desc);
if(ret < 0)
return ret;
ret = SetForwardFramePermanent(handle, usb_device_desc);
if(ret < 0)
return ret;
ret = UpdateFrameForwardEnable(handle, true, true, usb_device_desc);
if(ret < 0)
return ret;
return StartUsbCaptureDma(handle, usb_device_desc);
}
static int EndCapture(libusb_device_handle *handle, bool do_drain_frames, int start_frames, CaptureScreensType capture_type, const is_nitro_usb_device* usb_device_desc) {
if(usb_device_desc->is_capture)
return StopUsbCaptureDma(handle, usb_device_desc);
int ret = 0;
if(do_drain_frames)
ret = drain_frames(handle, FRAME_BUFFER_SIZE, start_frames, capture_type, usb_device_desc);
if(ret < 0)
return ret;
ret = StopUsbCaptureDma(handle, usb_device_desc);
if(ret < 0)
return ret;
return UpdateFrameForwardEnable(handle, false, false, usb_device_desc);
}
static void frame_wait(float single_frame_time, std::chrono::time_point<std::chrono::high_resolution_clock> clock_last_reset, int curr_frame_counter, int last_frame_counter) {
if(curr_frame_counter == 0)
return;
auto curr_time = std::chrono::high_resolution_clock::now();
std::chrono::duration<double> diff = curr_time - clock_last_reset;
float expected_time = single_frame_time * curr_frame_counter;
// If the current time is too low, sleep a bit to make sure we don't overrun the framerate counter
// Don't do it regardless of the situation, and only in small increments...
// Otherwise there is the risk of sleeping too much
while((diff.count() < expected_time) && ((expected_time - diff.count()) > (single_frame_time / 4)) && (!(last_frame_counter & (FRAME_BUFFER_SIZE - 1)))) {
default_sleep((expected_time - diff.count()) / 4);
curr_time = std::chrono::high_resolution_clock::now();
diff = curr_time - clock_last_reset;
}
}
static int reset_capture_frames(libusb_device_handle* handle, uint16_t &curr_frame_counter, uint16_t &last_frame_counter, float &single_frame_time, std::chrono::time_point<std::chrono::high_resolution_clock> &clock_last_reset, CaptureScreensType &curr_capture_type, CaptureScreensType wanted_capture_type, int multiplier, const is_nitro_usb_device* usb_device_desc) {
curr_frame_counter += 1;
if(curr_frame_counter == FRAME_BUFFER_SIZE) {
int ret = StopUsbCaptureDma(handle, usb_device_desc);
if(ret < 0)
return ret;
// If the user requests a mode change, accomodate them.
// Though it may lag for a bit...
if(wanted_capture_type != curr_capture_type) {
curr_capture_type = wanted_capture_type;
ret = set_capture_mode(handle, curr_capture_type, usb_device_desc);
if(ret < 0)
return ret;
}
uint16_t internalFrameCount = 0;
uint16_t full_internalFrameCount = 0;
int frame_diff = 0;
int diff_target = FRAME_BUFFER_SIZE * multiplier;
do {
// Check how many frames have passed...
ret = GetFrameCounter(handle, &internalFrameCount, usb_device_desc);
full_internalFrameCount = internalFrameCount;
// Sometimes the upper 8 bits aren't updated... Use only the lower 8 bits.
internalFrameCount &= 0xFF;
if(ret < 0)
return ret;
frame_diff = internalFrameCount - last_frame_counter;
if(frame_diff < 0)
frame_diff += 1 << 8;
// If the frames haven't advanced, the DS is either turned off or sleeping. If so, avoid locking up
if(frame_diff == 0)
break;
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_last_reset;
// If too much time has passed, the DS is probably either turned off or sleeping. If so, avoid locking up
if(diff.count() > (1.0 * multiplier)) {
frame_diff = 0;
break;
}
// Exit if enough frames have passed, or if there currently is some delay.
// Exiting early makes it possible to catch up to the DMA, if we're behind.
} while((frame_diff < diff_target) && (!(last_frame_counter & (FRAME_BUFFER_SIZE - 1))));
// Determine how much time a single frame takes. We'll use it for sleeps
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_last_reset;
if(frame_diff == 0)
single_frame_time = 0;
else
single_frame_time = diff.count() / (frame_diff / ((float)multiplier));
clock_last_reset = curr_time;
// Save the current frame counter's 8 LSB
last_frame_counter = internalFrameCount;
// If we're nearing 0xFFFF for the frame counter, reset it.
// It's a problematic value for DMA reading
if(frame_diff && (full_internalFrameCount >= 0xF000)) {
ret = UpdateFrameForwardEnable(handle, true, true, usb_device_desc);
if(ret < 0)
return ret;
clock_last_reset = std::chrono::high_resolution_clock::now();
}
ret = UpdateFrameForwardEnable(handle, true, false, usb_device_desc);
if(ret < 0)
return ret;
curr_frame_counter = 0;
// Start the actual DMA
if(single_frame_time > 0) {
ret = StartUsbCaptureDma(handle, usb_device_desc);
if(ret < 0)
return ret;
}
}
return LIBUSB_SUCCESS;
}
static void is_nitro_capture_capture_main_loop(CaptureData* capture_data) {
libusb_device_handle *handle = (libusb_device_handle*)capture_data->handle;
const is_nitro_usb_device* usb_device_desc = (const is_nitro_usb_device*)capture_data->status.device.descriptor;
CaptureScreensType curr_capture_type = capture_data->status.capture_type;
int ret = StartCaptureCapture(handle, curr_capture_type, usb_device_desc);
if(ret < 0) {
capture_error_print(true, capture_data, "Capture Start: Failed");
return;
}
int inner_curr_in = 0;
auto clock_start = std::chrono::high_resolution_clock::now();
auto clock_last_reset = std::chrono::high_resolution_clock::now();
while(capture_data->status.connected && capture_data->status.running) {
ret = ReadFrame(handle, (uint8_t*)&capture_data->capture_buf[inner_curr_in], _is_nitro_emulator_get_video_in_size(curr_capture_type), usb_device_desc);
if(ret < 0) {
capture_error_print(true, capture_data, "Disconnected: Read error");
break;
}
// Output to the other threads...
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_start;
clock_start = curr_time;
capture_data->time_in_buf[inner_curr_in] = diff.count();
capture_data->read[inner_curr_in] = _is_nitro_emulator_get_video_in_size(curr_capture_type);
capture_data->capture_type[inner_curr_in] = curr_capture_type;
inner_curr_in = (inner_curr_in + 1) % NUM_CONCURRENT_DATA_BUFFERS;
if(capture_data->status.cooldown_curr_in)
capture_data->status.cooldown_curr_in = capture_data->status.cooldown_curr_in - 1;
capture_data->status.curr_in = inner_curr_in;
capture_data->status.video_wait.unlock();
capture_data->status.audio_wait.unlock();
if(curr_capture_type != capture_data->status.capture_type) {
ret = EndCapture(handle, true, 0, curr_capture_type, usb_device_desc);
if(ret < 0) {
capture_error_print(true, capture_data, "Capture End: Failed");
return;
}
curr_capture_type = capture_data->status.capture_type;
ret = StartCaptureCapture(handle, curr_capture_type, usb_device_desc);
if(ret < 0) {
capture_error_print(true, capture_data, "Capture Restart: Failed");
return;
}
}
}
EndCapture(handle, true, 0, curr_capture_type, usb_device_desc);
}
static void is_nitro_emulator_capture_main_loop(CaptureData* capture_data) {
libusb_device_handle *handle = (libusb_device_handle*)capture_data->handle;
const is_nitro_usb_device* usb_device_desc = (const is_nitro_usb_device*)capture_data->status.device.descriptor;
uint16_t last_frame_counter = 0;
float single_frame_time = 0;
uint16_t curr_frame_counter = 0;
CaptureScreensType curr_capture_type = capture_data->status.capture_type;
int ret = StartEmulatorCapture(handle, last_frame_counter, single_frame_time, curr_capture_type, usb_device_desc);
if(ret < 0) {
capture_error_print(true, capture_data, "Capture Start: Failed");
return;
}
int inner_curr_in = 0;
auto clock_start = std::chrono::high_resolution_clock::now();
auto clock_last_reset = std::chrono::high_resolution_clock::now();
while(capture_data->status.connected && capture_data->status.running) {
frame_wait(single_frame_time, clock_last_reset, curr_frame_counter, last_frame_counter);
if(single_frame_time > 0) {
ret = ReadFrame(handle, (uint8_t*)&capture_data->capture_buf[inner_curr_in], _is_nitro_emulator_get_video_in_size(curr_capture_type), usb_device_desc);
if(ret < 0) {
capture_error_print(true, capture_data, "Disconnected: Read error");
break;
}
// Output to the other threads...
const auto curr_time = std::chrono::high_resolution_clock::now();
const std::chrono::duration<double> diff = curr_time - clock_start;
clock_start = curr_time;
capture_data->time_in_buf[inner_curr_in] = diff.count();
capture_data->read[inner_curr_in] = _is_nitro_emulator_get_video_in_size(curr_capture_type);
capture_data->capture_type[inner_curr_in] = curr_capture_type;
inner_curr_in = (inner_curr_in + 1) % NUM_CONCURRENT_DATA_BUFFERS;
if(capture_data->status.cooldown_curr_in)
capture_data->status.cooldown_curr_in = capture_data->status.cooldown_curr_in - 1;
capture_data->status.curr_in = inner_curr_in;
capture_data->status.video_wait.unlock();
capture_data->status.audio_wait.unlock();
}
else {
capture_data->status.cooldown_curr_in = FIX_PARTIAL_FIRST_FRAME_NUM;
default_sleep(20);
}
capture_data->status.curr_delay = last_frame_counter % FRAME_BUFFER_SIZE;
ret = reset_capture_frames(handle, curr_frame_counter, last_frame_counter, single_frame_time, clock_last_reset, curr_capture_type, capture_data->status.capture_type, 1, usb_device_desc);
if(ret < 0) {
capture_error_print(true, capture_data, "Disconnected: Frame counter reset error");
break;
}
}
EndCapture(handle, true, curr_frame_counter, curr_capture_type, usb_device_desc);
}
void is_nitro_capture_main_loop(CaptureData* capture_data) {
if(!usb_is_initialized())
return;
if(((const is_nitro_usb_device*)(capture_data->status.device.descriptor))->is_capture)
is_nitro_capture_capture_main_loop(capture_data);
else
is_nitro_emulator_capture_main_loop(capture_data);
}
void usb_is_nitro_capture_cleanup(CaptureData* capture_data) {
if(!usb_is_initialized())
return;
is_nitro_connection_end((libusb_device_handle*)capture_data->handle, (const is_nitro_usb_device*)capture_data->status.device.descriptor);
}
void usb_is_nitro_convertVideoToOutput(CaptureReceived *p_in, VideoOutputData *p_out, CaptureScreensType capture_type) {
if(!usb_is_initialized())
return;
int num_pixels = _is_nitro_emulator_get_video_in_size(capture_type) / 3;
int out_start_pos = 0;
int out_clear_pos = num_pixels;
if(capture_type == CAPTURE_SCREENS_BOTTOM) {
out_start_pos = num_pixels;
out_clear_pos = 0;
}
if((capture_type == CAPTURE_SCREENS_BOTTOM) || (capture_type == CAPTURE_SCREENS_TOP))
memset(p_out->screen_data[out_clear_pos], 0, num_pixels * 3);
for(int i = 0; i < num_pixels; i++) {
p_out->screen_data[i + out_start_pos][0] = p_in->is_nitro_capture_received.video_in.screen_data[i][2];
p_out->screen_data[i + out_start_pos][1] = p_in->is_nitro_capture_received.video_in.screen_data[i][1];
p_out->screen_data[i + out_start_pos][2] = p_in->is_nitro_capture_received.video_in.screen_data[i][0];
}
}
void usb_is_nitro_init() {
return usb_init();
}
void usb_is_nitro_close() {
usb_close();
}