#define AL_LOG_SECTION "cache_cdio" //#define AL_LOG_ENABLE_TRACE #include #include "../../codec/codec.h" #include "../backings/file.h" #include "../backings/memory.h" #include "../threaded_waits.h" #include "../range.h" #include "cdio.h" // 1 sector at a time to minimize skips. #define SECTORS_PER_STEP 1 #define BYTES_PER_STEP (SECTORS_PER_STEP * CDIO_CD_FRAMESIZE_RAW) static void cdio_log_messages(struct cch_handler_cdio *cdio) { char *error_messages = cdio_cddap_errors(cdio->drive); if (error_messages) { log_error("%s", error_messages); cdio_cddap_free_messages(error_messages); } char *messages = cdio_cddap_messages(cdio->drive); if (messages) { log_info("%s", messages); cdio_cddap_free_messages(messages); } } static bool handler_cdio_can_seek(struct cch_handler *handler) { (void)handler; return true; } static nn_thread_result NNWT_THREADCALL cd_read_thread(void *userdata) { struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)userdata; nn_thread_set_name("cdio_read"); for (;;) { if (!atomic_load(bool)(&cdio->running, AL_ATOMIC_RELAXED)) { // We don't care about how complete the read was. return 0; } // cdio_read() can block for a long time so we buffer each read before writing it to the backing. long ret = cdio_cddap_read(cdio->drive, nn_buffer_get_ptr(&cdio->buffer, 0), cdio->sector, SECTORS_PER_STEP); cdio_log_messages(cdio); al_assert(ret <= SECTORS_PER_STEP); off_t pointer = cdio->sector * CDIO_CD_FRAMESIZE_RAW; size_t size; u8 *ptr = cdio->backing->get_ptr(cdio->backing, pointer, &size); al_assert(size >= BYTES_PER_STEP); if (ret == SECTORS_PER_STEP) { al_memcpy(ptr, nn_buffer_get_ptr(&cdio->buffer, 0), BYTES_PER_STEP); } else if (ret == -10) { log_error("Disc read error, skipping sector."); al_memset(ptr, 0, BYTES_PER_STEP); ret = SECTORS_PER_STEP; } else if (ret < 0) { log_error("Fatal error (%d).", ret); cdio->backing->unlock(cdio->backing); break; } cch_backing_fill_range(cdio->backing, pointer, BYTES_PER_STEP); cdio->sector += ret; cdio->backing->unlock(cdio->backing); cch_threaded_waits_evaluate(&cdio->waits, cdio->backing); if (ret < SECTORS_PER_STEP || cdio->sector > cdio->end) { log_debug("Normal CD EOF."); break; } } if (cdio->sector <= cdio->end) { log_warn("Disc read cut short (sectors: %zd, end: %zd).", cdio->sector, cdio->end); // We didn't read the full disc, disable waiters. cch_threaded_waits_disable_all(&cdio->waits); } return 0; } static void handler_cdio_maybe_spawn_worker(struct cch_handler *handler, size_t index) { struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)handler; if (cdio->start == (lsn_t)index) return; if (atomic_load(bool)(&cdio->running, AL_ATOMIC_ACQUIRE)) { atomic_store(bool)(&cdio->running, false, AL_ATOMIC_RELEASE); nn_thread_join(&cdio->thread); } cdio->start = index; cdio->sector = cdio->start; atomic_store(bool)(&cdio->running, true, AL_ATOMIC_RELEASE); nn_thread_create(&cdio->thread, cd_read_thread, cdio); } static bool handler_cdio_wait_for_range(struct cch_handler *handler, struct cch_wait *wait) { struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)handler; return cch_threaded_wait_for_range(&cdio->waits, cdio->backing, wait); } static s32 handler_cdio_guess_format(struct cch_handler *handler) { (void)handler; return CAMU_CODEC_FALLBACK; } static void handler_cdio_free(struct cch_handler **handler) { struct cch_handler_cdio *cdio = (struct cch_handler_cdio *)*handler; cch_threaded_waits_disable_all(&cdio->waits); if (atomic_load(bool)(&cdio->running, AL_ATOMIC_RELAXED)) { atomic_store(bool)(&cdio->running, false, AL_ATOMIC_RELAXED); nn_thread_join(&cdio->thread); } if (cdio->drive) { cdio_log_messages(cdio); cdio_cddap_close(cdio->drive); } cch_threaded_waits_close(&cdio->waits); al_str_free(&cdio->handler.handler); al_free(cdio); *handler = NULL; } static bool open_cd_drive(struct cch_handler_cdio *cdio) { cdrom_drive_t *drive = NULL; char **cd_drives; CdIo_t *cdio_handle = NULL; cd_drives = cdio_get_devices_with_cap(NULL, CDIO_FS_AUDIO, false); if (cd_drives) { // Use first drive. cdio_handle = cdio_open(*cd_drives, DRIVER_UNKNOWN); drive = cdio_cddap_identify_cdio(cdio_handle, CDDA_MESSAGE_LOGIT, NULL); } else { log_error("No compatible disc found."); return false; } cdio_free_device_list(cd_drives); cdio_cddap_verbose_set(drive, CDDA_MESSAGE_LOGIT, CDDA_MESSAGE_LOGIT); s32 ret = cdio_cddap_open(drive); if (ret != 0) { log_error("Unable to open disc (%d).", ret); return false; } ret = cdio_cddap_speed_set(drive, 4); if (ret != 0) { log_warn("Failed to set drive speed (%d).", ret); } track_t tracks = cdio_cddap_tracks(drive); track_t first_track = cdio_get_first_track_num(drive->p_cdio); track_t last_track = cdio_get_last_track_num(drive->p_cdio); log_info("CD with %d tracks loaded (%d-%d).", tracks, first_track, last_track); al_array_reserve(cdio->handler.entry->chapters, tracks); lsn_t start = cdio_cddap_disc_firstsector(drive); lsn_t end = cdio_cddap_disc_lastsector(drive); lsn_t first_track_start = cdio_cddap_track_firstsector(drive, first_track); lsn_t offset = first_track_start; s32 track = CDIO_INVALID_TRACK; while (offset < end) { track = cdio_cddap_sector_gettrack(drive, offset); al_assert(track != CDIO_INVALID_TRACK); lsn_t track_start = cdio_cddap_track_firstsector(drive, track); lsn_t track_end = cdio_cddap_track_lastsector(drive, track); al_array_push(cdio->handler.entry->chapters, ((struct cch_chapter){ track_start, track_end })); log_trace("Track #%u: %zd-%zd.", track, track_start, track_end); offset = track_end + 1; } // end is a valid index so add one framesize. cdio->backing->set_size(cdio->backing, ((end - start) + 1) * CDIO_CD_FRAMESIZE_RAW, true); cdio->start = -1; cdio->end = end; cdio->drive = drive; cdio_log_messages(cdio); return true; } struct cch_entry *cch_handler_cdio_create(void) { struct cch_backing *backing = cch_backing_file_create(&al_str_c("/tmp/camu_cd_data"), 4096); if (!backing) return NULL; struct cch_handler_cdio *cdio = al_alloc_object(struct cch_handler_cdio); cdio->backing = backing; struct cch_entry *entry = al_alloc_object(struct cch_entry); entry->backing = backing; entry->handler = (struct cch_handler *)cdio; entry->handler->can_seek = handler_cdio_can_seek; entry->handler->maybe_spawn_worker = handler_cdio_maybe_spawn_worker; entry->handler->wait_for_range = handler_cdio_wait_for_range; entry->handler->guess_format = handler_cdio_guess_format; entry->handler->free = handler_cdio_free; entry->handler->entry = entry; al_str_from(&entry->handler->handler, "cdio"); if (!open_cd_drive(cdio)) { return NULL; } atomic_store(bool)(&cdio->running, false, AL_ATOMIC_RELAXED); nn_buffer_init(&cdio->buffer); nn_buffer_ensure_space(&cdio->buffer, BYTES_PER_STEP); cdio->buffer.size = BYTES_PER_STEP; cch_threaded_waits_init(&cdio->waits); return entry; }