diff options
Diffstat (limited to 'libtoolame-dab/vlc_input.c')
-rw-r--r-- | libtoolame-dab/vlc_input.c | 430 |
1 files changed, 430 insertions, 0 deletions
diff --git a/libtoolame-dab/vlc_input.c b/libtoolame-dab/vlc_input.c new file mode 100644 index 0000000..11e04c7 --- /dev/null +++ b/libtoolame-dab/vlc_input.c @@ -0,0 +1,430 @@ +#include "vlc_input.h" + +#if defined(VLC_INPUT) +#include <stdlib.h> +#include <errno.h> +#include <pthread.h> +#include <semaphore.h> +#include <assert.h> +#include <unistd.h> +#include <string.h> +#include <stdio.h> + +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) + |