|
|
|
@@ -92,6 +92,7 @@ typedef struct UDPContext { |
|
|
|
int circular_buffer_size; |
|
|
|
AVFifoBuffer *fifo; |
|
|
|
int circular_buffer_error; |
|
|
|
int64_t packet_gap; /* delay between transmitted packets */ |
|
|
|
#if HAVE_PTHREAD_CANCEL |
|
|
|
pthread_t circular_buffer_thread; |
|
|
|
pthread_mutex_t mutex; |
|
|
|
@@ -112,6 +113,7 @@ typedef struct UDPContext { |
|
|
|
#define E AV_OPT_FLAG_ENCODING_PARAM |
|
|
|
static const AVOption options[] = { |
|
|
|
{ "buffer_size", "System data size (in bytes)", OFFSET(buffer_size), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, |
|
|
|
{ "packet_gap", "Delay between packets", OFFSET(packet_gap), AV_OPT_TYPE_DURATION, { .i64 = 0 }, 0, INT_MAX, .flags = E }, |
|
|
|
{ "localport", "Local port", OFFSET(local_port), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, D|E }, |
|
|
|
{ "local_port", "Local port", OFFSET(local_port), AV_OPT_TYPE_INT, { .i64 = -1 }, -1, INT_MAX, .flags = D|E }, |
|
|
|
{ "localaddr", "Local address", OFFSET(localaddr), AV_OPT_TYPE_STRING, { .str = NULL }, .flags = D|E }, |
|
|
|
@@ -486,7 +488,7 @@ static int udp_get_file_handle(URLContext *h) |
|
|
|
} |
|
|
|
|
|
|
|
#if HAVE_PTHREAD_CANCEL |
|
|
|
static void *circular_buffer_task( void *_URLContext) |
|
|
|
static void *circular_buffer_task_rx( void *_URLContext) |
|
|
|
{ |
|
|
|
URLContext *h = _URLContext; |
|
|
|
UDPContext *s = h->priv_data; |
|
|
|
@@ -542,6 +544,81 @@ end: |
|
|
|
pthread_mutex_unlock(&s->mutex); |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
|
|
|
|
static void do_udp_write(void *arg, void *buf, int size) { |
|
|
|
URLContext *h = arg; |
|
|
|
UDPContext *s = h->priv_data; |
|
|
|
|
|
|
|
int ret; |
|
|
|
|
|
|
|
if (!(h->flags & AVIO_FLAG_NONBLOCK)) { |
|
|
|
ret = ff_network_wait_fd(s->udp_fd, 1); |
|
|
|
if (ret < 0) { |
|
|
|
s->circular_buffer_error = ret; |
|
|
|
return; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
if (!s->is_connected) { |
|
|
|
ret = sendto (s->udp_fd, buf, size, 0, |
|
|
|
(struct sockaddr *) &s->dest_addr, |
|
|
|
s->dest_addr_len); |
|
|
|
} else |
|
|
|
ret = send(s->udp_fd, buf, size, 0); |
|
|
|
|
|
|
|
s->circular_buffer_error=ret; |
|
|
|
} |
|
|
|
|
|
|
|
static void *circular_buffer_task_tx( void *_URLContext) |
|
|
|
{ |
|
|
|
URLContext *h = _URLContext; |
|
|
|
UDPContext *s = h->priv_data; |
|
|
|
int old_cancelstate; |
|
|
|
|
|
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelstate); |
|
|
|
|
|
|
|
for(;;) { |
|
|
|
int len; |
|
|
|
uint8_t tmp[4]; |
|
|
|
|
|
|
|
pthread_setcancelstate(PTHREAD_CANCEL_ENABLE, &old_cancelstate); |
|
|
|
|
|
|
|
av_usleep(s->packet_gap); |
|
|
|
|
|
|
|
pthread_setcancelstate(PTHREAD_CANCEL_DISABLE, &old_cancelstate); |
|
|
|
|
|
|
|
pthread_mutex_lock(&s->mutex); |
|
|
|
|
|
|
|
len=av_fifo_size(s->fifo); |
|
|
|
|
|
|
|
while (len<4) { |
|
|
|
if (pthread_cond_wait(&s->cond, &s->mutex) < 0) { |
|
|
|
goto end; |
|
|
|
} |
|
|
|
len=av_fifo_size(s->fifo); |
|
|
|
} |
|
|
|
|
|
|
|
av_fifo_generic_peek(s->fifo, tmp, 4, NULL); |
|
|
|
len=AV_RL32(tmp); |
|
|
|
|
|
|
|
if (len>0 && av_fifo_size(s->fifo)>=len+4) { |
|
|
|
av_fifo_drain(s->fifo, 4); /* skip packet length */ |
|
|
|
av_fifo_generic_read(s->fifo, h, len, do_udp_write); /* use function for write from fifo buffer */ |
|
|
|
if (s->circular_buffer_error == len) { |
|
|
|
/* all ok - reset error */ |
|
|
|
s->circular_buffer_error=0; |
|
|
|
} |
|
|
|
} |
|
|
|
|
|
|
|
pthread_mutex_unlock(&s->mutex); |
|
|
|
} |
|
|
|
|
|
|
|
end: |
|
|
|
pthread_mutex_unlock(&s->mutex); |
|
|
|
return NULL; |
|
|
|
} |
|
|
|
|
|
|
|
|
|
|
|
#endif |
|
|
|
|
|
|
|
static int parse_source_list(char *buf, char **sources, int *num_sources, |
|
|
|
@@ -650,6 +727,16 @@ static int udp_open(URLContext *h, const char *uri, int flags) |
|
|
|
"'circular_buffer_size' option was set but it is not supported " |
|
|
|
"on this build (pthread support is required)\n"); |
|
|
|
} |
|
|
|
if (av_find_info_tag(buf, sizeof(buf), "packet_gap", p)) { |
|
|
|
if (av_parse_time(&s->packet_gap, buf, 1)<0) { |
|
|
|
av_log(h, AV_LOG_ERROR, "Can't parse 'packet_gap'"); |
|
|
|
goto fail; |
|
|
|
} |
|
|
|
if (!HAVE_PTHREAD_CANCEL) |
|
|
|
av_log(h, AV_LOG_WARNING, |
|
|
|
"'packet_gap' option was set but it is not supported " |
|
|
|
"on this build (pthread support is required)\n"); |
|
|
|
} |
|
|
|
if (av_find_info_tag(buf, sizeof(buf), "localaddr", p)) { |
|
|
|
av_strlcpy(localaddr, buf, sizeof(localaddr)); |
|
|
|
} |
|
|
|
@@ -829,7 +916,18 @@ static int udp_open(URLContext *h, const char *uri, int flags) |
|
|
|
s->udp_fd = udp_fd; |
|
|
|
|
|
|
|
#if HAVE_PTHREAD_CANCEL |
|
|
|
if (!is_output && s->circular_buffer_size) { |
|
|
|
/* |
|
|
|
Create thread in case of: |
|
|
|
1. Input and circular_buffer_size is set |
|
|
|
2. Output and packet_gap and circular_buffer_size is set |
|
|
|
*/ |
|
|
|
|
|
|
|
if (is_output && s->packet_gap && !s->circular_buffer_size) { |
|
|
|
/* Warn user in case of 'circular_buffer_size' is not set */ |
|
|
|
av_log(h, AV_LOG_WARNING,"'packet_gap' option was set but 'circular_buffer_size' is not, but required\n"); |
|
|
|
} |
|
|
|
|
|
|
|
if ((!is_output && s->circular_buffer_size) || (is_output && s->packet_gap && s->circular_buffer_size)) { |
|
|
|
int ret; |
|
|
|
|
|
|
|
/* start the task going */ |
|
|
|
@@ -844,7 +942,7 @@ static int udp_open(URLContext *h, const char *uri, int flags) |
|
|
|
av_log(h, AV_LOG_ERROR, "pthread_cond_init failed : %s\n", strerror(ret)); |
|
|
|
goto cond_fail; |
|
|
|
} |
|
|
|
ret = pthread_create(&s->circular_buffer_thread, NULL, circular_buffer_task, h); |
|
|
|
ret = pthread_create(&s->circular_buffer_thread, NULL, is_output?circular_buffer_task_tx:circular_buffer_task_rx, h); |
|
|
|
if (ret != 0) { |
|
|
|
av_log(h, AV_LOG_ERROR, "pthread_create failed : %s\n", strerror(ret)); |
|
|
|
goto thread_fail; |
|
|
|
@@ -945,6 +1043,36 @@ static int udp_write(URLContext *h, const uint8_t *buf, int size) |
|
|
|
UDPContext *s = h->priv_data; |
|
|
|
int ret; |
|
|
|
|
|
|
|
#if HAVE_PTHREAD_CANCEL |
|
|
|
if (s->fifo) { |
|
|
|
uint8_t tmp[4]; |
|
|
|
|
|
|
|
pthread_mutex_lock(&s->mutex); |
|
|
|
|
|
|
|
/* |
|
|
|
Return error if last tx failed. |
|
|
|
Here we can't know on which packet error was, but it needs to know that error exists. |
|
|
|
*/ |
|
|
|
if (s->circular_buffer_error<0) { |
|
|
|
int err=s->circular_buffer_error; |
|
|
|
s->circular_buffer_error=0; |
|
|
|
pthread_mutex_unlock(&s->mutex); |
|
|
|
return err; |
|
|
|
} |
|
|
|
|
|
|
|
if(av_fifo_space(s->fifo) < size + 4) { |
|
|
|
/* What about a partial packet tx ? */ |
|
|
|
pthread_mutex_unlock(&s->mutex); |
|
|
|
return AVERROR(ENOMEM); |
|
|
|
} |
|
|
|
AV_WL32(tmp, size); |
|
|
|
av_fifo_generic_write(s->fifo, tmp, 4, NULL); /* size of packet */ |
|
|
|
av_fifo_generic_write(s->fifo, (uint8_t *)buf, size, NULL); /* the data */ |
|
|
|
pthread_cond_signal(&s->cond); |
|
|
|
pthread_mutex_unlock(&s->mutex); |
|
|
|
return size; |
|
|
|
} |
|
|
|
#endif |
|
|
|
if (!(h->flags & AVIO_FLAG_NONBLOCK)) { |
|
|
|
ret = ff_network_wait_fd(s->udp_fd, 1); |
|
|
|
if (ret < 0) |
|
|
|
|