/* * kissdx - KiSS PC-Link Daemon eXtended (based on kissd) * * This file Copyright (C) 2006 Vidar Tysse * This file is Public domain. * * Heavily based on wizd (Public domain) * */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "kissdx.h" #include "config.h" #include "dvdread.h" #include "utils.h" #include #include #ifdef USE_DVDNAV #include #endif #define WIZD_FILENAME_MAX 2048 #define SVI_FILENAME_LENGTH (255) #define JOINT_MAX (255) typedef struct { unsigned char name[WIZD_FILENAME_MAX]; u_int64_t size; off_t start_pos; // starting position in file } _FILE_INFO_T; typedef struct { int file_num; u_int64_t total_size; _FILE_INFO_T file[JOINT_MAX]; unsigned char iso_name[WIZD_FILENAME_MAX]; u_int64_t iso_seek; dvd_file_t* dvd_file; unsigned int current_file_num; } JOINT_FILE_INFO_T; int analyze_vob_file(char *vob_filename, JOINT_FILE_INFO_T *joint_file_info_p ) { char read_filename[SVI_FILENAME_LENGTH+10]; char vob_filepath[WIZD_FILENAME_MAX]; char first_filename[WIZD_FILENAME_MAX]; char series_filename[WIZD_FILENAME_MAX]; int ret; int i; int len; char *p; struct stat file_stat; logv0("analyze_vob_file() start."); logv("vob_filename='%s'", vob_filename); memset(read_filename, '\0', sizeof(read_filename)); memset(first_filename, '\0', sizeof(first_filename)); strncpy(vob_filepath, vob_filename, sizeof(vob_filepath)); logv("vob_filepath = '%s'", vob_filepath); strncpy(first_filename, vob_filepath, sizeof(vob_filepath)); logv("first_filename = '%s'", first_filename); ret = stat(first_filename, &file_stat); if ( ret != 0 ) { logv("'%s' Not found.", first_filename); return ( -1 ); } memset(joint_file_info_p, 0, sizeof(JOINT_FILE_INFO_T)); strncpy( (char*) joint_file_info_p->file[0].name, first_filename, sizeof(joint_file_info_p->file[0].name)); joint_file_info_p->file[0].size = file_stat.st_size; joint_file_info_p->total_size = file_stat.st_size; joint_file_info_p->file_num = 1; strncpy( series_filename, first_filename, sizeof(series_filename)); p = strrchr(series_filename, '.'); if (!p) return(-1); p++; if (strcasecmp(p, "vob") == 0) len = strlen(series_filename)-5; else if ((strcasecmp(p, "ts") == 0) || (strcasecmp(p, "tp") == 0)) len = strlen(series_filename)-4; else return(-1); for ( i=1; i0)) { series_filename[len]='0'; series_filename[len-1]++; } else { series_filename[len]++; } // եGET ret = stat(series_filename, &file_stat); if ( ret != 0 ) // ե̵?λ { logv("'%s' Not found.", series_filename); break; } logv("[%d]_filename = '%s'", i+1, series_filename); // GETե?joint_file_info_p ?˥å strncpy( (char*) joint_file_info_p->file[i].name, series_filename, sizeof(joint_file_info_p->file[0].name)); joint_file_info_p->file[i].size = file_stat.st_size; joint_file_info_p->total_size += file_stat.st_size; joint_file_info_p->file_num++; } joint_file_info_p->current_file_num = 0; logv0("analyze_vob_file() end."); return 0; } static void http_set_cells_to_pseudo_files(JOINT_FILE_INFO_T * joint_file_info_p, off_t *offset, off_t * joint_content_length, int titleid, int chapid, ifo_handle_t * vmg_file, ifo_handle_t * ifo_title, char *filename) { JOINT_FILE_INFO_T dummy_file; tt_srpt_t *tt_srpt; vts_ptt_srpt_t *vts_ptt_srpt; int ttn; int cur_cell; int pgc_id, pgn; off_t size; pgc_t *cur_pgc; int i, j; int cur_ind; int start_ind; int join_ind; int diff; off_t start_pos, start_off; off_t sector_offset = 0; int firstchap, lastchap; struct { off_t start_pos; off_t end_pos; } cellinfo[256]; tt_srpt = vmg_file->tt_srpt; ttn = tt_srpt->title[titleid].vts_ttn; vts_ptt_srpt = ifo_title->vts_ptt_srpt; joint_file_info_p->current_file_num = 0; joint_file_info_p->file[0].start_pos = -1; *joint_content_length = 0; /* loop through the chapters */ cur_ind = 0; cur_cell = 0; logv("Title %d has %d chapters, %d program chains", titleid + 1, tt_srpt->title[titleid].nr_of_ptts, ifo_title->vts_pgcit->nr_of_pgci_srp); (void)analyze_vob_file(filename, &dummy_file); //logv0("dummy_file info:"); //print_joint_files(&dummy_file); /* find all the cells that aren't < 1 sec. also only angle 1 */ firstchap = 0; lastchap = tt_srpt->title[titleid].nr_of_ptts; for (i = firstchap; i < lastchap; i++) { pgc_id = vts_ptt_srpt->title[ttn - 1].ptt[i].pgcn; pgn = vts_ptt_srpt->title[ttn - 1].ptt[i].pgn; cur_pgc = ifo_title->vts_pgcit->pgci_srp[pgc_id - 1].pgc; cur_cell = cur_pgc->program_map[pgn - 1] - 1; if (pgn == cur_pgc->nr_of_programs) diff = cur_pgc->nr_of_cells - cur_pgc->program_map[pgn - 1] + 1; else diff = cur_pgc->program_map[pgn] - cur_pgc->program_map[pgn - 1]; logv("Title %d, Chap %d, PGC %d, PGN %d has %d cells, cur_cell %d", titleid + 1, i + 1, pgc_id, pgn, diff, cur_cell); diff += cur_cell; /* loop through the cells of this chapter */ for (; cur_cell < diff; cur_cell++) { # if 1 if (cur_pgc->cell_playback[cur_cell].playback_time.second <= 1 && cur_pgc->cell_playback[cur_cell].playback_time.minute == 0 && cur_pgc->cell_playback[cur_cell].playback_time.hour == 0) { /* got a short one */ logv("skipping cell %d has start of %ld, end %ld", cur_cell + 1, (long)(cur_pgc->cell_playback[cur_cell].first_sector), (long)(cur_pgc->cell_playback[cur_cell].last_sector)); continue; } # endif # if 1 if (cur_pgc->cell_playback[cur_cell].block_type == 1 && cur_pgc->cell_playback[cur_cell].block_mode != 1) { /* angle is not angle 1 */ logv("skipping cell %d has angle of %d", cur_cell + 1, cur_pgc->cell_playback[cur_cell].block_mode); continue; } # endif if (((cur_pgc->cell_playback[cur_cell].first_sector+1) * 2048LL) >= dummy_file.total_size) { logv("skipping cell %d, first_sector not in vobs!", cur_cell + 1); //printf("skipping cell %d, first_sector not in vobs!\n", cur_cell + 1); continue; } cellinfo[cur_ind].start_pos = cur_pgc->cell_playback[cur_cell].first_sector * 2048LL; if (((cur_pgc->cell_playback[cur_cell].last_sector+1)* 2048LL) >= dummy_file.total_size) { logv("adjusting cell %d, last_sector not in vobs!", cur_cell + 1); //printf("adjusting cell %d, last_sector not in vobs!\n", cur_cell + 1); cellinfo[cur_ind].end_pos = dummy_file.total_size - 2048; } else cellinfo[cur_ind].end_pos = (cur_pgc->cell_playback[cur_cell].last_sector + 1) * 2048LL; logv("cell %d (ind %d) has start of %ld, end %ld, angle %d,%d", cur_cell + 1, cur_ind, (long)(cur_pgc->cell_playback[cur_cell].first_sector), (long)(cur_pgc->cell_playback[cur_cell].last_sector), cur_pgc->cell_playback[cur_cell].block_mode, cur_pgc->cell_playback[cur_cell].block_type); cur_ind++; // Keep track of the byte offset for the selected chapter if(i < chapid) { sector_offset += (cur_pgc->cell_playback[cur_cell].last_sector - cur_pgc->cell_playback[cur_cell].first_sector); logv("Chapter %d starts at sector %lld", i+2, sector_offset); } } } sector_offset *= 2048LL; logv("Adjusting offset by %lld bytes to get to the beginning of chapter %d", sector_offset, chapid+1); (*offset) += sector_offset; start_ind = 0; join_ind = 0; logv("Found %d non-zero cells", cur_ind); /* join together contiguous cells */ for (i = 0; i < cur_ind; i++) { if (i != cur_ind - 1) { if (cellinfo[i].end_pos == cellinfo[i + 1].start_pos) continue; } start_pos = cellinfo[start_ind].start_pos; logv("Joining cells %d thru %d:", start_ind + 1, i + 1); while (cellinfo[i].end_pos != 0) { size = 0; start_off = 0; for (j = 0; j < dummy_file.file_num; j++) { logv("compare %lld with %lld", (long long)start_pos, size + dummy_file.file[j].size); if (start_pos < size + dummy_file.file[j].size) { strcpy((char*) joint_file_info_p->file[join_ind].name, filename); joint_file_info_p->file[join_ind].name[strlen(filename) - 5] = '1' + j; start_off = start_pos - size; joint_file_info_p->file[join_ind].start_pos = start_off; if (cellinfo[i].end_pos <= size + dummy_file.file[j].size) { logv("end cell %d found in %s", i, joint_file_info_p->file[join_ind].name); joint_file_info_p->file[join_ind].size = cellinfo[i].end_pos - start_pos; size = cellinfo[i].end_pos - size - start_off; cellinfo[i].end_pos = 0; } else { logv("end cell %d NOT found in %s (%lld <= %lld)? %d", i, joint_file_info_p->file[join_ind].name, cellinfo[i].end_pos, size + dummy_file.file[j].size, cellinfo[i].end_pos <= size + dummy_file.file[j].size); joint_file_info_p->file[join_ind].size = dummy_file.file[j].size - start_off; size = joint_file_info_p->file[join_ind].size; } logv("%d: got a match, file %s, startoff %lld, size %lld, startpos %lld, end_pos %lld", i, joint_file_info_p->file[join_ind].name, start_off, size, start_pos, cellinfo[i].end_pos); break; } size += dummy_file.file[j].size; } if (j == dummy_file.file_num) { logv0("start_pos not found"); // just finish out this file and hope for the best! cellinfo[i].end_pos = 0; break; } logv(" Contiguous sectors %lld - %lld, %lld", start_pos / 2048LL, (start_pos + joint_file_info_p->file[join_ind].size) / 2048LL, joint_file_info_p->file[join_ind].size); start_pos += size; *joint_content_length += size; logv(" total size now %lld", *joint_content_length); join_ind++; } start_ind = i + 1; } joint_file_info_p->file_num = join_ind; joint_file_info_p->total_size = *joint_content_length; logv("total size %lld, number of chunks %d", *joint_content_length, join_ind); for (i = 0; i < joint_file_info_p->file_num; i++) { if (joint_file_info_p->file[i].start_pos == 0) logv0(" "); logv("[%d]: start %lld, size %lld, (%lld, %lld)", i, joint_file_info_p->file[i].start_pos, joint_file_info_p->file[i].size, joint_file_info_p->file[i].start_pos / 2048LL, (joint_file_info_p->file[i].start_pos + joint_file_info_p->file[i].size) / 2048LL); logv(" filename %s", joint_file_info_p->file[i].name); } } /*************** kissdx kissdx kissdx kissdx kissdx kissdx kissdx kissdx kissdx * ************* * * kissdx functions start here * * ************* ***************/ struct dvdreader_s { dvd_reader_t *dvd_reader; #ifdef USE_DVDNAV dvdnav_t *dvdnav; #endif }; #define MAX_DVD_TITLES 255 static struct dvdreader_s Cached_DVD_struct = {NULL}; static dvdreader_t Cached_DVD = NULL; // This pointer is only ever NULL or &Cached_DVD_struct static char Cached_DVDName[PATH_MAX] = {'\0'}; static JOINT_FILE_INFO_T Cached_joint_file_info; static uint8_t Cached_DVD_Current_title_set_nr = 1; static uint8_t Cached_DVD_title_set_nr[MAX_DVD_TITLES]; // Says which title set each title belongs to static int Cached_DVD_TitleCount = 0; static unsigned char data_buffer[64 * DVD_VIDEO_LB_LEN]; // The data buffer (we assume that the KiSS never asks for more than 128k) #ifdef USE_DVDNAV static struct dvdnav_status_s { int32_t TitleNo; int32_t ChapterNo; int32_t TitleBlockCount; int32_t TitleBlockPosition; } Cached_dvdnav_CurrentlyPlaying = {-1, -1, -1, -1}; #endif /* Save a table with the title set number of each title * of the supplied PartOfTitle Search Pointer Table. * This is needed later to ensure we have the correct * title set media file open for the requested title. * */ static void CachedDvdSaveTitleSetNumbers(tt_srpt_t *tt_srpt) { int i; for (i = 0; i < tt_srpt->nr_of_srpts && i < MAX_DVD_TITLES; i++) Cached_DVD_title_set_nr[i] = tt_srpt->title[i].title_set_nr; } /* Open a DVD (ISO or folder) at the specified title and return the descriptor. * Maintains a cached open descriptor for performance. * If the same DVD is already open, we switch to the specified title if needed. * */ dvdreader_t CachedDvdOpen(char *name, int titleNo) { ifo_handle_t *vmg_file = NULL; tt_srpt_t *tt_srpt; ifo_handle_t *vts_file = NULL; int titleid = titleNo > 0? titleNo - 1 : 0; int chapid = 0; int vts; off_t joint_content_length = 0; off_t offset = 0; int isSameDVD = !strcmp(name, Cached_DVDName); if (Cached_DVD != NULL && !isSameDVD) CachedDvdClose(); // Clean up, close all, reset globals if (!isSameDVD) { // This is a new or different DVD, not the one currently cached. Open and analyze it. switch (config.dvd_access_method) { case dvdam_libdvdread: Cached_joint_file_info.dvd_file = NULL; dvd_reader_t* dvd_reader = DVDOpen(name); if (dvd_reader) { vmg_file = ifoOpen( dvd_reader, 0 ); // open video manager file if( vmg_file ) { tt_srpt = vmg_file->tt_srpt; vts = tt_srpt->title[titleid].title_set_nr; Cached_DVD_Current_title_set_nr = vts; Cached_DVD_TitleCount = tt_srpt->nr_of_srpts; CachedDvdSaveTitleSetNumbers(tt_srpt); logv("There are %d titles on DVD '%s'", Cached_DVD_TitleCount, name); /* open the appropriate video title set */ vts_file = ifoOpen(dvd_reader, vts); if (vts_file) { /* make sure chapter is in this title set */ if (chapid < tt_srpt->title[titleid].nr_of_ptts) { logv("Found vts=%d, chapter=%d in title %d (in title set %d)", vts, chapid, titleid, tt_srpt->title[ titleid ].title_set_nr); http_set_cells_to_pseudo_files(&Cached_joint_file_info, &offset, &joint_content_length, titleid, chapid, vmg_file, vts_file, Cached_DVDName); } else { log("Invalid chapter=%d, title %d only has %d chapters", chapid, titleid, tt_srpt->title[titleid].nr_of_ptts); } if (titleid >= Cached_DVD_TitleCount) { log("Invalid title no. %d requested, DVD '%s' only has %d titles.", titleid + 1, name, Cached_DVD_TitleCount); } ifoClose( vts_file ); // This DVD seems ok, so we accept it for playback strcpy(Cached_DVDName, name); Cached_DVD_struct.dvd_reader = dvd_reader; Cached_DVD = &Cached_DVD_struct; } else log("No title found for vts=%d", vts); ifoClose( vmg_file ); } } break; #ifdef USE_DVDNAV case dvdam_libdvdnav: { dvdnav_t *dvdnav; char *dvd_userselected_language = "en"; if (dvdnav_open(&dvdnav, name) == DVDNAV_STATUS_OK) { /* set read ahead cache usage */ if (dvdnav_set_readahead_flag(dvdnav, 1) != DVDNAV_STATUS_OK) { log("Error from dvdnav_set_readahead_flag: %s", dvdnav_err_to_string(dvdnav)); dvdnav_close(dvdnav); return NULL; } /* set the language */ if (dvdnav_menu_language_select(dvdnav, dvd_userselected_language) != DVDNAV_STATUS_OK || dvdnav_audio_language_select(dvdnav, dvd_userselected_language) != DVDNAV_STATUS_OK || dvdnav_spu_language_select(dvdnav, dvd_userselected_language) != DVDNAV_STATUS_OK) { log("Error from dvdnav_menu_language_select: %s", dvdnav_err_to_string(dvdnav)); dvdnav_close(dvdnav); return NULL; } /* set the PGC positioning flag to have position information relatively to the * current chapter instead of relatively to the whole feature */ if (dvdnav_set_PGC_positioning_flag(dvdnav, 0) != DVDNAV_STATUS_OK) { log("Error from dvdnav_set_PGC_positioning_flag: %s", dvdnav_err_to_string(dvdnav)); dvdnav_close(dvdnav); return NULL; } int32_t titleCount; if (dvdnav_get_number_of_titles(dvdnav, &titleCount) != DVDNAV_STATUS_OK) { log("Error from dvdnav_get_number_of_titles: %s", dvdnav_err_to_string(dvdnav)); dvdnav_close(dvdnav); return NULL; } Cached_DVD_TitleCount = titleCount; logv("There are %d titles on DVD '%s'", Cached_DVD_TitleCount, name); // This DVD seems ok, so we accept it for playback Cached_DVD_Current_title_set_nr = titleNo; strcpy(Cached_DVDName, name); Cached_DVD_struct.dvdnav = dvdnav; Cached_DVD = &Cached_DVD_struct; } break; } #endif default: log("CachedDvdOpen: Unknown DVD access method %d.", (int)config.dvd_access_method); break; } } else { // This is the same DVD as the one currently cached. Check for title set change. switch (config.dvd_access_method) { case dvdam_libdvdread: if (Cached_DVD_title_set_nr[titleid] != Cached_DVD_Current_title_set_nr) { // This title is in a different title set. Close the current media file // and assign the new title set number so that the correct media file (VOB) will // be opened on the next call to EnsureTitlePlaying(). if (Cached_joint_file_info.dvd_file != NULL) { DVDCloseFile(Cached_joint_file_info.dvd_file); Cached_joint_file_info.dvd_file = NULL; } Cached_DVD_Current_title_set_nr = Cached_DVD_title_set_nr[titleid]; } break; #ifdef USE_DVDNAV case dvdam_libdvdnav: if (titleNo != Cached_dvdnav_CurrentlyPlaying.TitleNo) { // This is a different title. // Assign the new title number so that the correct title will // be opened on the next call to EnsureTitlePlaying(). Cached_DVD_Current_title_set_nr = titleNo; } break; #endif default: log("CachedDvdOpen: Unknown DVD access method %d.", (int)config.dvd_access_method); break; } } return Cached_DVD; } /* Close the cached DVD if open, and reset global variables. * */ void CachedDvdClose(void) { if (Cached_DVD != NULL) { switch (config.dvd_access_method) { case dvdam_libdvdread: if (Cached_joint_file_info.dvd_file != NULL) { DVDCloseFile(Cached_joint_file_info.dvd_file); Cached_joint_file_info.dvd_file = NULL; } DVDClose(Cached_DVD->dvd_reader); Cached_DVD->dvd_reader = NULL; break; #ifdef USE_DVDNAV case dvdam_libdvdnav: // Destroy the dvdnav handle if (dvdnav_close(Cached_DVD->dvdnav) != DVDNAV_STATUS_OK) { log("Error from dvdnav_close: %s", dvdnav_err_to_string(Cached_DVD->dvdnav)); } Cached_dvdnav_CurrentlyPlaying = (struct dvdnav_status_s) {-1, -1, -1, -1}; Cached_DVD->dvdnav = NULL; break; #endif default: log("CachedDvdClose: Unknown DVD access method %d.", (int)config.dvd_access_method); break; } Cached_DVD = NULL; } *Cached_DVDName = '\0'; Cached_DVD_Current_title_set_nr = 1; Cached_DVD_TitleCount = 0; } /* Get the number of titles in the current DVD * */ int CachedDvdGetTitleCount(void) { if (Cached_DVD != NULL) { return Cached_DVD_TitleCount; } else { log0("Internal: CachedDvdGetTitleCount was called when no DVD was open."); return 0; } } #ifdef USE_DVDNAV /* Do what's necessary to continue playback. * */ static void handle_dvdnav_packet(int32_t event) { // logv(">>> DVD event %d <<<", event); switch (event) { case DVDNAV_BLOCK_OK: // This is what we want: Data to send. We maintain the current block position counter here (and with dvdnav_get_position() after searching). Cached_dvdnav_CurrentlyPlaying.TitleBlockPosition++; break; case DVDNAV_STILL_FRAME: logv0(">>> Skipping STILL frame"); dvdnav_still_skip(Cached_DVD->dvdnav); break; case DVDNAV_WAIT: logv0(">>> Skipping WAIT frame"); dvdnav_wait_skip(Cached_DVD->dvdnav); break; case DVDNAV_CELL_CHANGE: dvdnav_current_title_info(Cached_DVD->dvdnav, &Cached_dvdnav_CurrentlyPlaying.TitleNo, &Cached_dvdnav_CurrentlyPlaying.ChapterNo); break; case DVDNAV_STOP: log0("handle_dvdnav_packet: Premature DVDNAV_STOP packet encountered."); break; } } #endif /* If not open already, open the DVD media file for the title that will be served. * */ static void EnsureTitlePlaying(void) { if (Cached_DVD != NULL) { switch (config.dvd_access_method) { case dvdam_libdvdread: if (Cached_joint_file_info.dvd_file == NULL) { Cached_joint_file_info.dvd_file = DVDOpenFile(Cached_DVD->dvd_reader, Cached_DVD_Current_title_set_nr, DVD_READ_TITLE_VOBS); if (Cached_joint_file_info.dvd_file != NULL) { logv0("Successfully opened selection as a DVD."); ssize_t blocks = DVDFileSize(Cached_joint_file_info.dvd_file); // Override the joint file info with the DVD info Cached_joint_file_info.total_size = (u_int64_t)blocks; Cached_joint_file_info.total_size *= 2048; strncpy( (char*) Cached_joint_file_info.file[0].name, Cached_DVDName, sizeof(Cached_joint_file_info.file[0].name)); Cached_joint_file_info.file[0].size = Cached_joint_file_info.total_size; Cached_joint_file_info.file_num = 1; } } break; #ifdef USE_DVDNAV case dvdam_libdvdnav: // Initiate playback from the start of the current title if not already playing if (Cached_dvdnav_CurrentlyPlaying.TitleNo != Cached_DVD_Current_title_set_nr) { uint8_t *data_buffer_p = NULL; int32_t blockLength, event; if (dvdnav_title_play(Cached_DVD->dvdnav, Cached_DVD_Current_title_set_nr) != DVDNAV_STATUS_OK) { log("EnsureTitlePlaying: Error from dvdnav_title_play (title #%n): %s", Cached_DVD_Current_title_set_nr, dvdnav_err_to_string(Cached_DVD->dvdnav)); return; } // Read forward to the first data packet, so we can determine the initial position correctly do { dvdnav_free_cache_block(Cached_DVD->dvdnav, data_buffer_p); data_buffer_p = data_buffer; if (dvdnav_get_next_cache_block(Cached_DVD->dvdnav, &data_buffer_p, &event, &blockLength) == DVDNAV_STATUS_ERR) { log("EnsureTitlePlaying: Error from dvdnav_get_next_cache_block: %s", dvdnav_err_to_string(Cached_DVD->dvdnav)); return; } handle_dvdnav_packet(event); } while (event != DVDNAV_BLOCK_OK && event != DVDNAV_STOP); if (event != DVDNAV_BLOCK_OK) { log("EnsureTitlePlaying: Unexpected event packet from dvdnav_get_next_cache_block: %n", event); dvdnav_free_cache_block(Cached_DVD->dvdnav, data_buffer_p); return; } if (dvdnav_get_position(Cached_DVD->dvdnav, (uint32_t*)&Cached_dvdnav_CurrentlyPlaying.TitleBlockPosition, (uint32_t*)&Cached_dvdnav_CurrentlyPlaying.TitleBlockCount) != DVDNAV_STATUS_OK) { log("EnsureTitlePlaying: Error from dvdnav_get_position: %s", dvdnav_err_to_string(Cached_DVD->dvdnav)); dvdnav_free_cache_block(Cached_DVD->dvdnav, data_buffer_p); return; } logv("Successfully opened selection as a DVD. Now at block position %d of %d.", Cached_dvdnav_CurrentlyPlaying.TitleBlockPosition - 1, Cached_dvdnav_CurrentlyPlaying.TitleBlockCount); // Make sure we have the current block in data_buffer when CachedDvdSendChunk() gets called if (data_buffer_p != data_buffer) memmove(data_buffer, data_buffer_p, DVD_VIDEO_LB_LEN); dvdnav_free_cache_block(Cached_DVD->dvdnav, data_buffer_p); } break; #endif default: log("EnsureTitlePlaying: Unknown DVD access method %d.", (int)config.dvd_access_method); break; } } else { log0("Internal: EnsureTitlePlaying was called when no DVD was open."); } } /* Get the size of the DVD media file that will be served. * */ u_int64_t CachedDvdGetSize(void) { if (Cached_DVD != NULL) { EnsureTitlePlaying(); switch (config.dvd_access_method) { case dvdam_libdvdread: if (Cached_joint_file_info.dvd_file == NULL) { log0("CachedDvdGetSize: DVDOpenFile failed."); return 0; } return Cached_joint_file_info.total_size; #ifdef USE_DVDNAV case dvdam_libdvdnav: if (Cached_dvdnav_CurrentlyPlaying.TitleBlockCount == -1) { log0("CachedDvdGetSize: Title playback has not been started properly."); return 0; } return Cached_dvdnav_CurrentlyPlaying.TitleBlockCount * DVD_VIDEO_LB_LEN; #endif default: log("CachedDvdGetSize: Unknown DVD access method %d.", (int)config.dvd_access_method); return 0; } } else { log0("Internal: CachedDvdGetSize was called when no DVD was open."); return 0; } } /* Get a chunk of data from the DVD media file that we serve * */ ssize_t CachedDvdSendChunk(int socket, off_t range_start_pos, size_t range_length) { if (Cached_DVD != NULL) { ssize_t sentLen = 0; // Calculate which logical blocks to read from the DVD title int startBlock = range_start_pos / DVD_VIDEO_LB_LEN; int startBlockOffset = range_start_pos % DVD_VIDEO_LB_LEN; size_t readLength = startBlockOffset + range_length; size_t blockCount = readLength / DVD_VIDEO_LB_LEN; if (blockCount * DVD_VIDEO_LB_LEN < readLength) blockCount++; // Read a final block that will be sent only partially logv("DVD-reading %d blocks at %d for request to send %d bytes at %lld.", blockCount, startBlock, range_length, (long long)range_start_pos); EnsureTitlePlaying(); switch (config.dvd_access_method) { case dvdam_libdvdread: { if (range_length > sizeof(data_buffer)) { log("CachedDvdSendChunk: ASSERTION FAILED!!! Cannot read %u bytes because internal buffer is only %u bytes in size.", range_length, sizeof(data_buffer)); return -1; } if (Cached_joint_file_info.dvd_file == NULL) { log0("CachedDvdSendChunk: DVDOpenFile failed."); return -1; } // Read the blocks ssize_t len = DVDReadBlocks(Cached_joint_file_info.dvd_file, startBlock, blockCount, data_buffer ); if (len <= 0) { log("CachedDvdSendChunk: DVDReadBlocks failed for %u blocks at block offset %u.", blockCount, startBlock); return -1; } // Send the bytes sentLen = write(socket, data_buffer + startBlockOffset, range_length); if (sentLen != range_length) { log("CachedDvdSendChunk: write to socket failed for %u bytes at offset %llu. Wrote %d bytes.", range_length, range_start_pos, sentLen); return -1; } break; } #ifdef USE_DVDNAV case dvdam_libdvdnav: { /* STRATEGY USING LIBDVDNAV: * We use libdvdnav's internal caching, so we want to use dvdnav_get_next_cache_block() whenever we can. * We estimate (consulting max_cache_trust_blocks) whether reading forward using dvdnav_get_next_cache_block() * will bring us to the position where we must start reading our data within a reasonable time. * If that is the case, we read forward in a loop until we get there. * If not so, we use dvdnav_sector_search() to bring us directly to the closest position before the point * where we must start reading, and then we read forward in a loop until we get there. * When this positioning is done, we read the blocks that we need using dvdnav_get_next_cache_block(). * * We keep the current block in data_buffer and will re-use that block without reading it again if the * next request is for data that starts from the same block. Since we use libdvdnav's internal caching, * we must copy the current block from the cache into data_buffer before returning. * * We maintain a current block counter in Cached_dvdnav_CurrentlyPlaying.TitleBlockPosition at all times. * We set this counter explicitly by calling dvdnav_get_position() after each dvdnav_sector_search(), and * we increase it each time we sequentially read a data packet from the DVD. * This counter starts at 1 (for the first data block) so we subtract 1 into currentBlock for the logic. * * */ const static int max_cache_trust_blocks = 200; int32_t blockLength; // Assume we have a valid data block in data_buffer from the previous invocation uint8_t *data_buffer_p = data_buffer; int32_t event = DVDNAV_BLOCK_OK; int32_t currentBlock = Cached_dvdnav_CurrentlyPlaying.TitleBlockPosition - 1; logv("----------> Starting currentBlock == %d.", currentBlock); if (Cached_dvdnav_CurrentlyPlaying.TitleBlockCount == -1) { log0("CachedDvdSendChunk: Title playback has not been started properly."); return 0; } if (startBlock + blockCount > Cached_dvdnav_CurrentlyPlaying.TitleBlockCount + 1) { log("CachedDvdSendChunk: Blocks were requested beyond end of title (%d blocks starting at %d). Title %d has only %d blocks.", blockCount, startBlock, Cached_dvdnav_CurrentlyPlaying.ChapterNo, Cached_dvdnav_CurrentlyPlaying.TitleBlockCount); return 0; } if (currentBlock < 0 // First data block in title has not been read yet (should not happen) || startBlock < currentBlock // We are moving to a data block earlier than the current one (rewind?) || startBlock > currentBlock + max_cache_trust_blocks) // We are exceeding cache confidence and will search instead of looping { logv("----------> dvdnav_sector_search to == %d.", startBlock); // We must search if (dvdnav_sector_search(Cached_DVD->dvdnav, startBlock, SEEK_SET) != DVDNAV_STATUS_OK) { log("CachedDvdSendChunk: Error from dvdnav_sector_search: %s", dvdnav_err_to_string(Cached_DVD->dvdnav)); return -1; } // Read forward to the first DVDNAV_BLOCK_OK packet, so we get the correct TitleBlockPosition do { data_buffer_p = data_buffer; if (dvdnav_get_next_cache_block(Cached_DVD->dvdnav, &data_buffer_p, &event, &blockLength) == DVDNAV_STATUS_ERR) { log("CachedDvdSendChunk (searching): Error from dvdnav_get_next_cache_block: %s", dvdnav_err_to_string(Cached_DVD->dvdnav)); return -1; } handle_dvdnav_packet(event); dvdnav_free_cache_block(Cached_DVD->dvdnav, data_buffer_p); } while (event != DVDNAV_BLOCK_OK && event != DVDNAV_STOP); if (event != DVDNAV_BLOCK_OK) { log("CachedDvdSendChunk (searching): Unexpected event packet from dvdnav_get_next_cache_block: %n", event); return -1; } if (dvdnav_get_position(Cached_DVD->dvdnav, (uint32_t*)&Cached_dvdnav_CurrentlyPlaying.TitleBlockPosition, (uint32_t*)&Cached_dvdnav_CurrentlyPlaying.TitleBlockCount) != DVDNAV_STATUS_OK) { log("CachedDvdSendChunk (searching): Error from dvdnav_get_position: %s", dvdnav_err_to_string(Cached_DVD->dvdnav)); return -1; } currentBlock = Cached_dvdnav_CurrentlyPlaying.TitleBlockPosition - 1; logv("----------> GetPos> currentBlock == %d.", currentBlock); } // Read forward until we have the first data block from which to send data while (currentBlock < startBlock) { dvdnav_free_cache_block(Cached_DVD->dvdnav, data_buffer_p); data_buffer_p = data_buffer; if (dvdnav_get_next_cache_block(Cached_DVD->dvdnav, &data_buffer_p, &event, &blockLength) != DVDNAV_STATUS_OK) { log("CachedDvdSendChunk: Error from dvdnav_get_next_cache_block: %s", dvdnav_err_to_string(Cached_DVD->dvdnav)); return -1; } handle_dvdnav_packet(event); currentBlock = Cached_dvdnav_CurrentlyPlaying.TitleBlockPosition - 1; logv("----------> GetNex> currentBlock == %d.", currentBlock); } // Positioning is done. Now read the blocks and send them. ssize_t remainingLen = range_length; while (remainingLen > 0 && currentBlock < startBlock + blockCount) { if (event == DVDNAV_BLOCK_OK) { logv("----------> WRITE-> currentBlock == %d, remaining == %d.", currentBlock, remainingLen); // Send the bytes of this block uint8_t* blockSendStart = data_buffer_p; size_t blockSendLen = DVD_VIDEO_LB_LEN; if (currentBlock == startBlock && startBlockOffset) { // Send a partial first block blockSendStart += startBlockOffset; blockSendLen -= startBlockOffset; } if (currentBlock == startBlock + blockCount - 1) { // This could be a partial last block if (remainingLen > blockSendLen) { log("CachedDvdSendChunk: Sanity check failed. Remaining length on last block is too big: %d. Expected <= %d.", remainingLen, blockSendLen); return -1; } blockSendLen = remainingLen; } ssize_t blockSentLen = write(socket, blockSendStart, blockSendLen); if (blockSentLen != blockSendLen) { if (blockSentLen < 0) log("CachedDvdSendChunk: write to socket failed for %d bytes at offset %d: %s", blockSendLen, currentBlock * DVD_VIDEO_LB_LEN + (blockSendStart - data_buffer_p), strerror(errno)); else log("CachedDvdSendChunk: write to socket failed for %d bytes at offset %d. Wrote %d bytes.", blockSendLen, currentBlock * DVD_VIDEO_LB_LEN + (blockSendStart - data_buffer_p), blockSentLen); return -1; } remainingLen -= blockSentLen; sentLen += blockSentLen; // Make sure we have the current block in data_buffer when we are called the next time if (remainingLen == 0 && data_buffer_p != data_buffer) memmove(data_buffer, data_buffer_p, DVD_VIDEO_LB_LEN); } // Read on if we have not yet sent the requested data if (remainingLen > 0) { dvdnav_free_cache_block(Cached_DVD->dvdnav, data_buffer_p); data_buffer_p = data_buffer; if (dvdnav_get_next_cache_block(Cached_DVD->dvdnav, &data_buffer_p, &event, &blockLength) != DVDNAV_STATUS_OK) { log("CachedDvdSendChunk: Error from dvdnav_get_next_cache_block: %s", dvdnav_err_to_string(Cached_DVD->dvdnav)); return -1; } handle_dvdnav_packet(event); currentBlock = Cached_dvdnav_CurrentlyPlaying.TitleBlockPosition - 1; } } dvdnav_free_cache_block(Cached_DVD->dvdnav, data_buffer_p); break; } #endif default: log("CachedDvdSendChunk: Unknown DVD access method %d.", (int)config.dvd_access_method); return -1; } // Return number of bytes sent return sentLen; } else { log0("Internal: CachedDvdSendChunk was called when no DVD was open."); return -1; } } /*************** kissdx kissdx kissdx kissdx kissdx kissdx kissdx kissdx kissdx * ************* * * kissdx functions END here * * ************* ***************/