#include "vlc_input.h" #if defined(VLC_INPUT) #include #include #include #include #include #include #include #include int check_vlc_uses_size_t(); struct vlc_buffer* vlc_buffer_new(); void vlc_buffer_free(struct vlc_buffer* node); libvlc_instance_t *m_vlc; libvlc_media_player_t *m_mp; unsigned int vlc_rate; unsigned int vlc_channels; struct vlc_buffer *head_buffer; // now playing information can get written to // a file. This writing happens in a separate thread #define NOWPLAYING_LEN 512 char vlc_nowplaying[NOWPLAYING_LEN]; int vlc_nowplaying_running; pthread_t vlc_nowplaying_thread; const char* vlc_nowplaying_filename; struct icywriter_task_data { char text[NOWPLAYING_LEN]; int success; sem_t sem; }; struct icywriter_task_data icy_task_data; pthread_mutex_t buffer_lock = PTHREAD_MUTEX_INITIALIZER; struct vlc_buffer* vlc_buffer_new() { struct vlc_buffer* node; node = malloc(sizeof(struct vlc_buffer)); memset(node, 0, sizeof(struct vlc_buffer)); return node; } void vlc_buffer_free(struct vlc_buffer* node) { if (node->buf) { free(node->buf); } free(node); } size_t vlc_buffer_totalsize(struct vlc_buffer* node) { size_t totalsize = 0; for (; node != NULL; node = node->next) { totalsize += node->size; } return totalsize; } // VLC Audio prerender callback, we must allocate a buffer here void prepareRender_size_t( void* p_audio_data, uint8_t** pp_pcm_buffer, size_t size) { *pp_pcm_buffer = malloc(size); } void prepareRender( void* p_audio_data, uint8_t** pp_pcm_buffer, unsigned int size) { *pp_pcm_buffer = malloc(size); } // Audio postrender callback void handleStream_size_t( void* p_audio_data, uint8_t* p_pcm_buffer, unsigned int channels, unsigned int rate, unsigned int nb_samples, unsigned int bits_per_sample, size_t size, int64_t pts) { assert(channels == vlc_channels); assert(rate == vlc_rate); assert(bits_per_sample == 16); // 16 is a bit arbitrary, if it's too small we might enter // a deadlock if toolame asks for too much data const size_t max_length = 16 * size; for (;;) { pthread_mutex_lock(&buffer_lock); if (vlc_buffer_totalsize(head_buffer) < max_length) { struct vlc_buffer* newbuf = vlc_buffer_new(); newbuf->buf = p_pcm_buffer; newbuf->size = size; // Append the new buffer to the end of the linked list struct vlc_buffer* tail = head_buffer; while (tail->next) { tail = tail->next; } tail->next = newbuf; pthread_mutex_unlock(&buffer_lock); return; } pthread_mutex_unlock(&buffer_lock); usleep(100); } } // convert from unsigned int size to size_t size void handleStream( void* p_audio_data, uint8_t* p_pcm_buffer, unsigned int channels, unsigned int rate, unsigned int nb_samples, unsigned int bits_per_sample, unsigned int size, int64_t pts) { handleStream_size_t( p_audio_data, p_pcm_buffer, channels, rate, nb_samples, bits_per_sample, size, pts); } int vlc_in_prepare( unsigned verbosity, unsigned int rate, const char* uri, unsigned channels, const char* icy_write_file ) { fprintf(stderr, "Initialising VLC...\n"); vlc_nowplaying_running = 0; vlc_nowplaying_filename = icy_write_file; long long int handleStream_address; long long int prepareRender_address; int vlc_version_check = check_vlc_uses_size_t(); if (vlc_version_check == 0) { fprintf(stderr, "You are using VLC with unsigned int size callbacks\n"); handleStream_address = (long long int)(intptr_t)(void*)&handleStream; prepareRender_address = (long long int)(intptr_t)(void*)&prepareRender; } else if (vlc_version_check == 1) { fprintf(stderr, "You are using VLC with size_t size callbacks\n"); handleStream_address = (long long int)(intptr_t)(void*)&handleStream_size_t; prepareRender_address = (long long int)(intptr_t)(void*)&prepareRender_size_t; } else { fprintf(stderr, "Error detecting VLC version!\n"); fprintf(stderr, " you are using %s\n", libvlc_get_version()); return -1; } vlc_rate = rate; vlc_channels = channels; // VLC options char smem_options[512]; snprintf(smem_options, sizeof(smem_options), "#transcode{acodec=s16l,samplerate=%d}:" // We are using transcode because smem only support raw audio and // video formats "smem{" "audio-postrender-callback=%lld," "audio-prerender-callback=%lld" "}", vlc_rate, handleStream_address, prepareRender_address); char verb_options[512]; snprintf(verb_options, sizeof(verb_options), "--verbose=%d", verbosity); const char * const vlc_args[] = { verb_options, "--sout", smem_options // Stream to memory }; // Launch VLC m_vlc = libvlc_new(sizeof(vlc_args) / sizeof(vlc_args[0]), vlc_args); // Load the media libvlc_media_t *m; m = libvlc_media_new_location(m_vlc, uri); m_mp = libvlc_media_player_new_from_media(m); libvlc_media_release(m); // Allocate the list head_buffer = vlc_buffer_new(); // Start playing int ret = libvlc_media_player_play(m_mp); if (ret == 0) { libvlc_media_t *media = libvlc_media_player_get_media(m_mp); libvlc_state_t st; ret = -1; int timeout; for (timeout = 0; timeout < 100; timeout++) { st = libvlc_media_get_state(media); usleep(10*1000); if (st != libvlc_NothingSpecial) { ret = 0; break; } } } return ret; } ssize_t vlc_in_read(void *buf, size_t len) { if (len == 0) { return 0; } assert(buf); size_t requested = len; for (;;) { pthread_mutex_lock(&buffer_lock); if (vlc_buffer_totalsize(head_buffer) >= len) { while (len >= head_buffer->size) { if (head_buffer->buf && head_buffer->size) { // Get all the data from this list element memcpy(buf, head_buffer->buf, head_buffer->size); buf += head_buffer->size; len -= head_buffer->size; } if (head_buffer->next) { struct vlc_buffer *next_head = head_buffer->next; vlc_buffer_free(head_buffer); head_buffer = next_head; } else { vlc_buffer_free(head_buffer); head_buffer = vlc_buffer_new(); break; } } if (len > 0) { assert(len < head_buffer->size); assert(head_buffer->buf); memcpy(buf, head_buffer->buf, len); // split the current head into two parts size_t remaining = head_buffer->size - len; uint8_t *newbuf = malloc(remaining); memcpy(newbuf, head_buffer->buf + len, remaining); free(head_buffer->buf); head_buffer->buf = newbuf; head_buffer->size = remaining; } pthread_mutex_unlock(&buffer_lock); return requested; } pthread_mutex_unlock(&buffer_lock); usleep(100); libvlc_media_t *media = libvlc_media_player_get_media(m_mp); libvlc_state_t st = libvlc_media_get_state(media); if (!(st == libvlc_Opening || st == libvlc_Buffering || st == libvlc_Playing) ) { return -1; } char* nowplaying_sz = libvlc_media_get_meta(media, libvlc_meta_NowPlaying); if (nowplaying_sz) { snprintf(vlc_nowplaying, NOWPLAYING_LEN, "%s", nowplaying_sz); free(nowplaying_sz); } } abort(); } // This task is run in a separate thread void* vlc_in_write_icy_task(void* arg) { struct icywriter_task_data* data = arg; FILE* fd = fopen(vlc_nowplaying_filename, "wb"); if (fd) { int ret = fputs(data->text, fd); fclose(fd); if (ret >= 0) { data->success = 1; } } else { data->success = 0; } sem_post(&data->sem); return NULL; } void vlc_in_write_icy(void) { if (vlc_nowplaying_filename == NULL) { return; } else if (vlc_nowplaying_running == 0) { memcpy(icy_task_data.text, vlc_nowplaying, NOWPLAYING_LEN); icy_task_data.success = 0; int ret = sem_init(&icy_task_data.sem, 0, 0); if (ret == 0) { ret = pthread_create(&vlc_nowplaying_thread, NULL, vlc_in_write_icy_task, &icy_task_data); if (ret == 0) { vlc_nowplaying_running = 1; } else { fprintf(stderr, "ICY Text writer: thread start failed: %s\n", strerror(ret)); } } else { fprintf(stderr, "ICY Text writer: semaphore init failed: %s\n", strerror(errno)); } } else { int ret = sem_trywait(&icy_task_data.sem); if (ret == -1 && errno == EAGAIN) { return; } else if (ret == 0) { ret = pthread_join(vlc_nowplaying_thread, NULL); if (ret != 0) { fprintf(stderr, "ICY Text writer: pthread_join error: %s\n", strerror(ret)); } vlc_nowplaying_running = 0; } else { fprintf(stderr, "ICY Text writer: semaphore trywait failed: %s\n", strerror(errno)); } } } /* VLC up to version 2.1.0 used a different callback function signature. * VLC 2.2.0 uses size_t * * \return 1 if the callback with size_t size should be used. * 0 if the callback with unsigned int size should be used. * -1 if there was an error. */ int check_vlc_uses_size_t() { int retval = -1; char libvlc_version[256]; strncpy(libvlc_version, libvlc_get_version(), 255); char *space_position = strstr(libvlc_version, " "); if (space_position) { *space_position = '\0'; } char *saveptr; char *major_ver_sz = strtok_r(libvlc_version, ".", &saveptr); if (major_ver_sz) { int major_ver = atoi(major_ver_sz); char *minor_ver_sz = strtok_r(NULL, ".", &saveptr); if (minor_ver_sz) { int minor_ver = atoi(minor_ver_sz); retval = (major_ver >= 2 && minor_ver >= 2) ? 1 : 0; } } return retval; } #endif // defined(VLC_INPUT)