icecast_client.c
Go to the documentation of this file.
1 /**
2  * @file icecast_client.c
3  * @brief Icecast client
4  *
5  * @section License
6  *
7  * Copyright (C) 2010-2018 Oryx Embedded SARL. All rights reserved.
8  *
9  * This file is part of CycloneTCP Open.
10  *
11  * This program is free software; you can redistribute it and/or
12  * modify it under the terms of the GNU General Public License
13  * as published by the Free Software Foundation; either version 2
14  * of the License, or (at your option) any later version.
15  *
16  * This program is distributed in the hope that it will be useful,
17  * but WITHOUT ANY WARRANTY; without even the implied warranty of
18  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
19  * GNU General Public License for more details.
20  *
21  * You should have received a copy of the GNU General Public License
22  * along with this program; if not, write to the Free Software Foundation,
23  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
24  *
25  * @author Oryx Embedded SARL (www.oryx-embedded.com)
26  * @version 1.9.0
27  **/
28 
29 //Switch to the appropriate trace level
30 #define TRACE_LEVEL ICECAST_TRACE_LEVEL
31 
32 //Dependencies
33 #include <stdlib.h>
34 #include "icecast/icecast_client.h"
35 #include "str.h"
36 #include "debug.h"
37 
38 //Check TCP/IP stack configuration
39 #if (ICECAST_CLIENT_SUPPORT == ENABLED)
40 
41 
42 /**
43  * @brief Initialize settings with default values
44  * @param[out] settings Structure that contains Icecast client settings
45  **/
46 
48 {
49  //Use default interface
50  settings->interface = netGetDefaultInterface();
51 
52  //Icecast server name
53  strcpy(settings->serverName, "");
54  //Icecast server port
55  settings->serverPort = 8000;
56  //Requested resource
57  strcpy(settings->resource, "/stream");
58 
59  //Streaming buffer size
60  settings->bufferSize = 80000;
61 }
62 
63 
64 /**
65  * @brief Icecast client initialization
66  * @param[in] context Pointer to the Icecast client context
67  * @param[in] settings Icecast client specific settings
68  * @return Error code
69  **/
70 
72  const IcecastClientSettings *settings)
73 {
74  error_t error;
75 
76  //Debug message
77  TRACE_INFO("Initializing Icecast client...\r\n");
78 
79  //Ensure the parameters are valid
80  if(!context || !settings)
82 
83  //Clear the Icecast client context
84  memset(context, 0, sizeof(IcecastClientContext));
85 
86  //Save user settings
87  context->settings = *settings;
88  //Get the size of the circular buffer
89  context->bufferSize = settings->bufferSize;
90 
91  //Start of exception handling block
92  do
93  {
94  //Allocate a memory block to hold the circular buffer
95  context->streamBuffer = osAllocMem(context->bufferSize);
96  //Failed to allocate memory?
97  if(!context->streamBuffer)
98  {
99  //Report an error to the calling function
100  error = ERROR_OUT_OF_MEMORY;
101  break;
102  }
103 
104  //Create mutex object to protect critical sections
105  if(!osCreateMutex(&context->mutex))
106  {
107  //Failed to create mutex
108  error = ERROR_OUT_OF_RESOURCES;
109  break;
110  }
111 
112  //Create event to get notified when the buffer is writable
113  if(!osCreateEvent(&context->writeEvent))
114  {
115  //Failed to create event object
116  error = ERROR_OUT_OF_RESOURCES;
117  break;
118  }
119 
120  //The buffer is available for writing
121  osSetEvent(&context->writeEvent);
122 
123  //Create event to get notified when the buffer is readable
124  if(!osCreateEvent(&context->readEvent))
125  {
126  //Failed to create event object
127  error = ERROR_OUT_OF_RESOURCES;
128  break;
129  }
130 
131  //Successful initialization
132  error = NO_ERROR;
133 
134  //End of exception handling block
135  } while(0);
136 
137  //Check whether an error occurred
138  if(error)
139  {
140  //Clean up side effects...
141  osFreeMem(context->streamBuffer);
142  osDeleteMutex(&context->mutex);
143  osDeleteEvent(&context->writeEvent);
144  osDeleteEvent(&context->readEvent);
145  }
146 
147  //Return status code
148  return error;
149 }
150 
151 
152 /**
153  * @brief Start Icecast client
154  * @param[in] context Pointer to the Icecast client context
155  * @return Error code
156  **/
157 
159 {
160  OsTask *task;
161 
162  //Debug message
163  TRACE_INFO("Starting Icecast client...\r\n");
164 
165  //Create the Icecast client task
166  task = osCreateTask("Icecast client", icecastClientTask,
168 
169  //Unable to create the task?
170  if(task == OS_INVALID_HANDLE)
171  return ERROR_OUT_OF_RESOURCES;
172 
173  //Successful processing
174  return NO_ERROR;
175 }
176 
177 
178 /**
179  * @brief Copy data from input stream
180  * @param[in] context Pointer to the Icecast client context
181  * @param[out] data Pointer to the user buffer
182  * @param[in] size Maximum number of bytes that can be read
183  * @param[out] length Number of bytes that have been read
184  * @param[in] timeout Maximum time to wait before returning
185  * @return Error code
186  **/
187 
189  uint8_t *data, size_t size, size_t *length, systime_t timeout)
190 {
191  bool_t status;
192 
193  //Ensure the parameters are valid
194  if(!context || !data)
196 
197  //Wait for the buffer to be available for reading
198  status = osWaitForEvent(&context->readEvent, timeout);
199  //Timeout error?
200  if(!status)
201  return ERROR_TIMEOUT;
202 
203  //Enter critical section
204  osAcquireMutex(&context->mutex);
205  //Compute the number of bytes to read at a time
206  *length = MIN(size, context->bufferLength);
207  //Leave critical section
208  osReleaseMutex(&context->mutex);
209 
210  //Check whether the specified data crosses buffer boundaries
211  if((context->readIndex + *length) <= context->bufferSize)
212  {
213  //Copy the data
214  memcpy(data, context->streamBuffer + context->readIndex, *length);
215  }
216  else
217  {
218  //Copy the first part of the data
219  memcpy(data, context->streamBuffer + context->readIndex,
220  context->bufferSize - context->readIndex);
221  //Wrap around to the beginning of the circular buffer
222  memcpy(data + context->bufferSize - context->readIndex, context->streamBuffer,
223  *length - context->bufferSize + context->readIndex);
224  }
225 
226  //Enter critical section
227  osAcquireMutex(&context->mutex);
228 
229  //Increment read index
230  context->readIndex += *length;
231  //Wrap around if necessary
232  if(context->readIndex >= context->bufferSize)
233  context->readIndex -= context->bufferSize;
234 
235  //Update buffer length
236  context->bufferLength -= *length;
237  //Check whether the buffer is available for writing
238  if(context->bufferLength < context->bufferSize)
239  osSetEvent(&context->writeEvent);
240  //Check whether the buffer is available for reading
241  if(context->bufferLength > 0)
242  osSetEvent(&context->readEvent);
243 
244  //Leave critical section
245  osReleaseMutex(&context->mutex);
246 
247  //Successful read operation
248  return NO_ERROR;
249 }
250 
251 
252 /**
253  * @brief Copy metadata from input stream
254  * @param[in] context Pointer to the Icecast client context
255  * @param[out] metadata Pointer to the user buffer
256  * @param[in] size Maximum number of bytes that can be read
257  * @param[out] length Number of bytes that have been read
258  * @return Error code
259  **/
260 
262  char_t *metadata, size_t size, size_t *length)
263 {
264  //Ensure the parameters are valid
265  if(!context || !metadata)
267 
268  //Enter critical section
269  osAcquireMutex(&context->mutex);
270 
271  //Limit the number of data to read
272  *length = MIN(size, context->metadataLength);
273  //Save metadata information
274  memcpy(metadata, context->metadata, *length);
275 
276  //Leave critical section
277  osReleaseMutex(&context->mutex);
278 
279  //Successful read operation
280  return NO_ERROR;
281 }
282 
283 
284 /**
285  * @brief Icecast client task
286  * @param[in] param Pointer to the Icecast client context
287  **/
288 
289 void icecastClientTask(void *param)
290 {
291  error_t error;
292  bool_t end;
293  size_t n;
294  size_t length;
295  size_t received;
296  IcecastClientContext *context;
297 
298  //Retrieve the Icecast client context
299  context = (IcecastClientContext *) param;
300 
301  //Main loop
302  while(1)
303  {
304  //Debug message
305  TRACE_INFO("Icecast client: Connecting to server %s port %" PRIu16 "\r\n",
306  context->settings.serverName, context->settings.serverPort);
307 
308  //Initiate a connection to the Icecast server
309  error = icecastClientConnect(context);
310 
311  //Connection to server failed?
312  if(error)
313  {
314  //Debug message
315  TRACE_ERROR("Icecast client: Connection to server failed!\r\n");
316  //Recovery delay
318  //Try to reconnect...
319  continue;
320  }
321 
322  //Debug message
323  TRACE_INFO("Block size = %" PRIuSIZE "\r\n", context->blockSize);
324 
325  //Check block size
326  if(!context->blockSize)
327  {
328  //Close socket
329  socketClose(context->socket);
330  //Recovery delay
332  //Try to reconnect...
333  continue;
334  }
335 
336  //Initialize loop condition variable
337  end = FALSE;
338 
339  //Read as much data as possible...
340  while(!end)
341  {
342  //Process the stream block by block
343  length = context->blockSize;
344 
345  //Read current block
346  while(!end && length > 0)
347  {
348  //Wait for the buffer to be available for writing
350 
351  //Enter critical section
352  osAcquireMutex(&context->mutex);
353  //Compute the number of bytes to read at a time
354  n = MIN(length, context->bufferSize - context->bufferLength);
355  //Leave critical section
356  osReleaseMutex(&context->mutex);
357 
358  //Check whether the specified data crosses buffer boundaries
359  if((context->writeIndex + n) > context->bufferSize)
360  n = context->bufferSize - context->writeIndex;
361 
362  //Receive data
363  error = socketReceive(context->socket, context->streamBuffer +
364  context->writeIndex, n, &received, SOCKET_FLAG_WAIT_ALL);
365 
366  //Any error to report?
367  if(error)
368  {
369  //Stop streaming data
370  end = TRUE;
371  }
372  else
373  {
374  //Enter critical section
375  osAcquireMutex(&context->mutex);
376 
377  //Increment write index
378  context->writeIndex += n;
379  //Wrap around if necessary
380  if(context->writeIndex >= context->bufferSize)
381  context->writeIndex -= context->bufferSize;
382 
383  //Update buffer length
384  context->bufferLength += n;
385  //Check whether the buffer is available for writing
386  if(context->bufferLength < context->bufferSize)
387  osSetEvent(&context->writeEvent);
388  //Check whether the buffer is available for reading
389  if(context->bufferLength > 0)
390  osSetEvent(&context->readEvent);
391 
392  //Leave critical section
393  osReleaseMutex(&context->mutex);
394 
395  //Update the total number of bytes that have been received
396  context->totalLength += n;
397  //Number of remaining data to read
398  length -= n;
399  }
400  }
401 
402  //Debug message
403  TRACE_DEBUG("Icecast client: Total bytes received = %" PRIuSIZE "\r\n", context->totalLength);
404 
405  //Check whether the metadata block should be read
406  if(!end)
407  {
408  //Process the metadata block
409  error = icecastClientProcessMetadata(context);
410  //Any error to report?
411  if(error)
412  end = TRUE;
413  }
414  }
415 
416  //Close connection
417  socketClose(context->socket);
418  }
419 }
420 
421 
422 /**
423  * @brief Connect to the specified Icecast server
424  * @param[in] context Pointer to the Icecast client context
425  **/
426 
428 {
429  error_t error;
430  size_t length;
431  uint16_t serverPort;
432  IpAddr serverIpAddr;
433  NetInterface *interface;
434 
435  //Underlying network interface
436  interface = context->settings.interface;
437 
438  //Force traffic to go through a proxy server?
439  if(strcmp(interface->proxyName, ""))
440  {
441  //Icecast request template
442  const char_t requestTemplate[] =
443  "GET http://%s:%" PRIu16 "%s HTTP/1.1\r\n"
444  "Host: %s:%" PRIu16 "\r\n"
445  "User-agent: UserAgent\r\n"
446  "Icy-MetaData: 1\r\n"
447  "Connection: close\r\n"
448  "\r\n";
449 
450  //Format Icecast request
451  length = sprintf(context->buffer, requestTemplate,
452  context->settings.serverName, context->settings.serverPort,
453  context->settings.resource, context->settings.serverName,
454  context->settings.serverPort);
455 
456  //The specified proxy server can be either an IP or a host name
457  error = getHostByName(interface, interface->proxyName, &serverIpAddr, 0);
458  //Unable to resolve server name?
459  if(error)
460  return error;
461 
462  //Proxy server port
463  serverPort = interface->proxyPort;
464  }
465  else
466  {
467  //Icecast request template
468  const char_t requestTemplate[] =
469  "GET %s HTTP/1.1\r\n"
470  "Host: %s\r\n"
471  "User-agent: UserAgent\r\n"
472  "Icy-MetaData: 1\r\n"
473  "Connection: close\r\n"
474  "\r\n";
475 
476  //Format Icecast request
477  length = sprintf(context->buffer, requestTemplate,
478  context->settings.resource, context->settings.serverName);
479 
480  //The specified Icecast server can be either an IP or a host name
481  error = getHostByName(interface, context->settings.serverName, &serverIpAddr, 0);
482  //Unable to resolve server name?
483  if(error)
484  return error;
485 
486  //Icecast server port
487  serverPort = context->settings.serverPort;
488  }
489 
490  //Open a TCP socket
492  //Failed to open socket?
493  if(context->socket == NULL)
494  return ERROR_OUT_OF_RESOURCES;
495 
496  //Start of exception handling block
497  do
498  {
499  //Associate the socket with the relevant interface
500  error = socketBindToInterface(context->socket, interface);
501  //Unable to bind the socket to the desired interface?
502  if(error)
503  break;
504 
505  //Adjust receive timeout
507  //Any error to report?
508  if(error)
509  break;
510 
511  //Connect to the server
512  error = socketConnect(context->socket, &serverIpAddr, serverPort);
513  //Connection with server failed?
514  if(error)
515  break;
516 
517  //Display Icecast request for debugging purpose
518  TRACE_DEBUG(context->buffer);
519 
520  //Send request to the server
521  error = socketSend(context->socket, context->buffer,
523  //Failed to send the request?
524  if(error)
525  break;
526 
527  //Parse response header
528  while(1)
529  {
530  char_t *separator;
531  char_t *property;
532  char_t *value;
533 
534  //Read a line from the response header
535  error = socketReceive(context->socket, context->buffer,
537  //Failed to read data?
538  if(error)
539  break;
540 
541  //Properly terminate the string with a NULL character
542  context->buffer[length] = '\0';
543 
544  //The end of the header has been reached?
545  if(!strcmp(context->buffer, "\r\n"))
546  break;
547 
548  //Check whether a separator is present
549  separator = strchr(context->buffer, ':');
550 
551  //Separator found?
552  if(separator)
553  {
554  //Split the line
555  *separator = '\0';
556 
557  //Get property name and value
558  property = strTrimWhitespace(context->buffer);
559  value = strTrimWhitespace(separator + 1);
560 
561  //Debug message
562  TRACE_INFO("<%s>=<%s>\r\n", property, value);
563 
564  //Icy-Metaint property found?
565  if(!strcasecmp(property, "Icy-Metaint"))
566  {
567  //Retrieve the block size used by the Icecast server
568  context->blockSize = atoi(value);
569  }
570  }
571  }
572 
573  //End of exception handling block
574  } while(0);
575 
576  //Check whether an error occurred
577  if(error)
578  {
579  //Clean up side effects
580  socketClose(context->socket);
581  }
582 
583  //Return status code
584  return error;
585 }
586 
587 
588 /**
589  * @brief Decode metadata block
590  * @param[in] context Pointer to the Icecast client context
591  **/
592 
594 {
595  error_t error;
596  size_t n;
597  size_t length;
598  size_t metadataLength;
599 
600  //The metadata block begins with a single byte which indicates
601  //how many 16-byte segments need to be read
602  error = socketReceive(context->socket, context->buffer,
603  sizeof(uint8_t), &length, SOCKET_FLAG_WAIT_ALL);
604 
605  //Any error to report?
606  if(error)
607  return error;
608  //Make sure the expected number of bytes have been received
609  if(length != sizeof(uint8_t))
610  return ERROR_INVALID_METADATA;
611 
612  //Compute the length of the following metadata block
613  metadataLength = context->buffer[0] * 16;
614  //Limit the number of bytes to read
615  n = MIN(metadataLength, ICECAST_CLIENT_METADATA_MAX_SIZE - 1);
616 
617  //Read the metadata information
618  error = socketReceive(context->socket, context->buffer,
620 
621  //Any error to report?
622  if(error)
623  return error;
624  //Make sure the expected number of bytes have been received
625  if(length != n)
626  return ERROR_INVALID_METADATA;
627 
628  //Enter critical section
629  osAcquireMutex(&context->mutex);
630 
631  //Save metadata information
632  memcpy(context->metadata, context->buffer, n);
633  //Terminate the string properly
634  context->metadata[n] = '\0';
635  //Record the length of the metadata
636  context->metadataLength = n;
637 
638  //Leave critical section
639  osReleaseMutex(&context->mutex);
640 
641  //Any metadata information received?
642  if(n > 0)
643  {
644  //Debug message
645  TRACE_DEBUG("Icecast client: Metadata = <%s>\r\n", context->metadata);
646  }
647 
648  //Compute the number of bytes that have not been processed
649  metadataLength -= n;
650 
651  //Read the complete metadata
652  while(metadataLength > 0)
653  {
654  //Compute the number of data to read at a time
655  n = MIN(metadataLength, ICECAST_CLIENT_METADATA_MAX_SIZE);
656 
657  //Drop incoming data...
658  error = socketReceive(context->socket, context->buffer,
660 
661  //Any error to report?
662  if(error)
663  return error;
664  //Make sure the expected number of bytes have been received
665  if(length != n)
666  return ERROR_INVALID_METADATA;
667 
668  //Update byte counter
669  metadataLength -= n;
670  }
671 
672  //Successful processing
673  return NO_ERROR;
674 }
675 
676 #endif
677 
void osDeleteMutex(OsMutex *mutex)
Delete a mutex object.
uint32_t systime_t
Definition: compiler_port.h:44
error_t socketReceive(Socket *socket, void *data, size_t size, size_t *received, uint_t flags)
Receive data from a connected socket.
Definition: socket.c:584
Icecast client settings.
bool_t osCreateMutex(OsMutex *mutex)
Create a mutex object.
char char_t
Definition: compiler_port.h:41
size_t bufferSize
Streaming buffer size.
Icecast client.
IcecastClientSettings settings
User settings.
void osFreeMem(void *p)
Release a previously allocated memory block.
Debugging facilities.
OsEvent readEvent
This event tells whether the buffer is readable.
#define ICECAST_CLIENT_STACK_SIZE
Socket * socket
Underlying socket.
error_t getHostByName(NetInterface *interface, const char_t *name, IpAddr *ipAddr, uint_t flags)
Resolve a host name into an IP address.
Definition: socket.c:1049
#define ICECAST_CLIENT_TIMEOUT
#define ICECAST_CLIENT_METADATA_MAX_SIZE
OsTask * osCreateTask(const char_t *name, OsTaskCode taskCode, void *param, size_t stackSize, int_t priority)
Create a new task.
error_t icecastClientInit(IcecastClientContext *context, const IcecastClientSettings *settings)
Icecast client initialization.
OsMutex mutex
Mutex protecting critical sections.
Icecast client context.
Invalid parameter.
Definition: error.h:45
char_t serverName[ICECAST_SERVER_NAME_MAX_LEN]
Icecast server name.
IP network address.
Definition: ip.h:57
void socketClose(Socket *socket)
Close an existing socket.
Definition: socket.c:797
#define TRACE_ERROR(...)
Definition: debug.h:70
size_t blockSize
Number of data bytes between subsequent metadata blocks.
String manipulation helper functions.
error_t icecastClientProcessMetadata(IcecastClientContext *context)
Decode metadata block.
size_t readIndex
Current read index within the buffer.
char_t metadata[ICECAST_CLIENT_METADATA_MAX_SIZE]
Metadata information.
bool_t osCreateEvent(OsEvent *event)
Create an event object.
size_t writeIndex
Current write index within the buffer.
#define TRUE
Definition: os_port.h:48
#define strcasecmp
error_t icecastClientStart(IcecastClientContext *context)
Start Icecast client.
size_t totalLength
Total number of bytes that have been received.
error_t socketSetTimeout(Socket *socket, systime_t timeout)
Set timeout value for blocking operations.
Definition: socket.c:216
size_t bufferSize
Streaming buffer size.
Task object.
error_t icecastClientConnect(IcecastClientContext *context)
Connect to the specified Icecast server.
void icecastClientTask(void *param)
Icecast client task.
NetInterface * interface
Underlying network interface.
#define INFINITE_DELAY
Definition: os_port.h:72
void osDelayTask(systime_t delay)
Delay routine.
size_t metadataLength
Length of the metadata.
NetInterface * netGetDefaultInterface(void)
Get default network interface.
Definition: net.c:1495
OsEvent writeEvent
This event tells whether the buffer is writable.
#define MIN(a, b)
Definition: os_port.h:60
#define ICECAST_RECOVERY_DELAY
#define TRACE_INFO(...)
Definition: debug.h:86
Success.
Definition: error.h:42
error_t socketConnect(Socket *socket, const IpAddr *remoteIpAddr, uint16_t remotePort)
Establish a connection to a specified socket.
Definition: socket.c:357
void * osAllocMem(size_t size)
Allocate a memory block.
void osSetEvent(OsEvent *event)
Set the specified event object to the signaled state.
error_t
Error codes.
Definition: error.h:40
bool_t osWaitForEvent(OsEvent *event, systime_t timeout)
Wait until the specified event is in the signaled state.
error_t icecastClientReadStream(IcecastClientContext *context, uint8_t *data, size_t size, size_t *length, systime_t timeout)
Copy data from input stream.
Socket * socketOpen(uint_t type, uint_t protocol)
Create a socket (UDP or TCP)
Definition: socket.c:92
void osReleaseMutex(OsMutex *mutex)
Release ownership of the specified mutex object.
uint8_t data[]
Definition: dtls_misc.h:167
#define PRIuSIZE
Definition: compiler_port.h:72
#define NetInterface
Definition: net.h:34
char_t buffer[ICECAST_CLIENT_METADATA_MAX_SIZE]
Memory buffer for input/output operations.
uint8_t value[]
Definition: dtls_misc.h:141
char_t * strTrimWhitespace(char_t *s)
Removes all leading and trailing whitespace from a string.
Definition: str.c:68
void osDeleteEvent(OsEvent *event)
Delete an event object.
size_t bufferLength
Streaming buffer length.
error_t icecastClientReadMetadata(IcecastClientContext *context, char_t *metadata, size_t size, size_t *length)
Copy metadata from input stream.
void icecastClientGetDefaultSettings(IcecastClientSettings *settings)
Initialize settings with default values.
uint16_t serverPort
Icecast server port.
#define OS_INVALID_HANDLE
Definition: os_port.h:77
#define ICECAST_CLIENT_PRIORITY
uint8_t length
Definition: dtls_misc.h:140
uint8_t n
uint8_t * streamBuffer
Streaming buffer.
#define FALSE
Definition: os_port.h:44
error_t socketSend(Socket *socket, const void *data, size_t length, size_t *written, uint_t flags)
Send data to a connected socket.
Definition: socket.c:490
int bool_t
Definition: compiler_port.h:47
void osAcquireMutex(OsMutex *mutex)
Acquire ownership of the specified mutex object.
error_t socketBindToInterface(Socket *socket, NetInterface *interface)
Bind a socket to a particular network interface.
Definition: socket.c:309
char_t resource[ICECAST_RESOURCE_MAX_LEN]
Requested resource.
#define TRACE_DEBUG(...)
Definition: debug.h:98