Motion substream (#454)

Add option to output a sub-stream from Motion
This commit is contained in:
tosiara
2017-10-15 19:29:43 +03:00
committed by Mr-Dave
parent 2eedfdc015
commit 7d8ba03cfc
12 changed files with 187 additions and 47 deletions

12
conf.c
View File

@@ -80,6 +80,7 @@ struct config conf_template = {
.ffmpeg_video_codec = DEF_FFMPEG_CODEC,
.ipv6_enabled = 0,
.stream_port = 0,
.substream_port = 0,
.stream_quality = 50,
.stream_motion = 0,
.stream_maxrate = 1,
@@ -1054,6 +1055,17 @@ config_param config_params[] = {
print_int
},
{
"substream_port",
"\n############################################################\n"
"# Live Substream Server\n"
"############################################################\n\n"
"# The mini-http server listens to this port for requests (default: 0 = disabled)",
0,
CONF_OFFSET(substream_port),
copy_int,
print_int
},
{
"stream_quality",
"# Quality of the jpeg (in percent) images produced (default: 50)",
0,

1
conf.h
View File

@@ -70,6 +70,7 @@ struct config {
const char *ffmpeg_video_codec;
int ipv6_enabled;
int stream_port;
int substream_port;
int stream_quality;
int stream_motion;
int stream_maxrate;

10
event.c
View File

@@ -334,7 +334,10 @@ static void event_stop_stream(struct context *cnt,
struct timeval *tv1 ATTRIBUTE_UNUSED)
{
if ((cnt->conf.stream_port) && (cnt->stream.socket != -1))
stream_stop(cnt);
stream_stop(&cnt->stream);
if ((cnt->conf.substream_port) && (cnt->substream.socket != -1))
stream_stop(&cnt->substream);
}
static void event_stream_put(struct context *cnt,
@@ -343,7 +346,10 @@ static void event_stream_put(struct context *cnt,
void *dummy2 ATTRIBUTE_UNUSED, struct timeval *tv1 ATTRIBUTE_UNUSED)
{
if (cnt->conf.stream_port)
stream_put(cnt, img);
stream_put(cnt, &cnt->stream, &cnt->stream_count, img, 0);
if (cnt->conf.substream_port)
stream_put(cnt, &cnt->substream, &cnt->substream_count, img, 1);
}

View File

@@ -466,6 +466,9 @@ ipv6_enabled off
# The mini-http server listens to this port for requests (default: 0 = disabled)
stream_port 8081
# 50% scaled down substream (default: 0 = disabled)
# substream_port 8082
# Quality of the jpeg (in percent) images produced (default: 50)
stream_quality 50
@@ -495,6 +498,7 @@ stream_auth_method 0
; stream_authentication username:password
# Percentage to scale the stream image for preview
# This is scaled on the browser side, motion will keep sending full frames
# Default: 25
; stream_preview_scale 25

View File

@@ -1131,7 +1131,8 @@ static int motion_init(struct context *cnt)
/* Initialize stream server if stream port is specified to not 0 */
if (cnt->conf.stream_port) {
if (stream_init(cnt) == -1) {
if (stream_init (&(cnt->stream), cnt->conf.stream_port, cnt->conf.stream_localhost,
cnt->conf.ipv6_enabled) == -1) {
MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "Problem enabling motion-stream server in port %d",
cnt->conf.stream_port);
cnt->conf.stream_port = 0;
@@ -1142,6 +1143,30 @@ static int motion_init(struct context *cnt)
}
}
/* Initialize 50% scaled substream server if substream port is specified to not 0
But only if dimensions are 8-modulo after scaling. Otherwise disable substream */
if (cnt->conf.substream_port){
if ((cnt->conf.width / 2) % 8 == 0 && (cnt->conf.height / 2) % 8 == 0
&& cnt->imgs.type == VIDEO_PALETTE_YUV420P){
if (stream_init (&(cnt->substream), cnt->conf.substream_port, cnt->conf.stream_localhost,
cnt->conf.ipv6_enabled) == -1) {
MOTION_LOG(ERR, TYPE_ALL, SHOW_ERRNO, "Problem enabling motion-substream server in port %d",
cnt->conf.substream_port);
cnt->conf.substream_port = 0;
cnt->finish = 1;
} else {
MOTION_LOG(NTC, TYPE_ALL, NO_ERRNO, "Started motion-substream server on port %d (auth %s)",
cnt->conf.substream_port, cnt->conf.stream_auth_method ? "Enabled":"Disabled");
}
}
else
{
/* TODO support GRAY scale */
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "Subtream does not support GRAY, and original resolution must be modulo of 16");
cnt->conf.substream_port = 0;
}
}
/* Prevent first few frames from triggering motion... */
cnt->moved = 8;
/* Initialize the double sized characters if needed. */

View File

@@ -448,6 +448,10 @@ struct context {
struct stream stream;
int stream_count;
struct stream substream;
int substream_count;
#if defined(HAVE_MYSQL) || defined(HAVE_PGSQL) || defined(HAVE_SQLITE3)
int sql_mask;
#endif

View File

@@ -1660,6 +1660,11 @@ Some configuration options are only used if Motion is built on a system that has
<td align="left">stream_port</td>
<td align="left"><a href="#stream_port" >stream_port</a></td>
</tr>
<tr>
<td height="17" align="left">&nbsp;</td>
<td align="left">&nbsp;</td>
<td align="left"><a href="#substream_port" >substream_port</a></td>
</tr>
<tr>
<td height="17" align="left"><br></td>
<td align="left">stream_preview_newline</td>
@@ -2231,22 +2236,23 @@ Some configuration options are only used if Motion is built on a system that has
<tr>
<td bgcolor="#edf4f9" ><a href="#ipv6_enabled" >ipv6_enabled</a> </td>
<td bgcolor="#edf4f9" ><a href="#stream_port" >stream_port</a> </td>
<td bgcolor="#edf4f9" ><a href="#substream_port" >substream_port</a> </td>
<td bgcolor="#edf4f9" ><a href="#stream_quality" >stream_quality</a> </td>
<td bgcolor="#edf4f9" ><a href="#stream_motion" >stream_motion</a> </td>
</tr>
<tr>
<td bgcolor="#edf4f9" ><a href="#stream_maxrate" >stream_maxrate</a> </td>
<td bgcolor="#edf4f9" ><a href="#stream_motion" >stream_motion</a> </td>
<td bgcolor="#edf4f9" ><a href="#stream_maxrate" >stream_maxrate</a> </td>
<td bgcolor="#edf4f9" ><a href="#stream_localhost" >stream_localhost</a> </td>
<td bgcolor="#edf4f9" ><a href="#stream_limit" >stream_limit</a> </td>
<td bgcolor="#edf4f9" ><a href="#stream_auth_method" >stream_auth_method</a> </td>
</tr>
<tr>
<td bgcolor="#edf4f9" ><a href="#stream_auth_method" >stream_auth_method</a> </td>
<td bgcolor="#edf4f9" ><a href="#stream_authentication" >stream_authentication</a> </td>
<td bgcolor="#edf4f9" ><a href="#stream_preview_scale" >stream_preview_scale</a> </td>
<td bgcolor="#edf4f9" ><a href="#stream_preview_newline" >stream_preview_newline</a> </td>
<td bgcolor="#edf4f9" ><a href="#webcontrol_port" >webcontrol_port</a> </td>
</tr>
<tr>
<td bgcolor="#edf4f9" ><a href="#webcontrol_port" >webcontrol_port</a> </td>
<td bgcolor="#edf4f9" ><a href="#webcontrol_localhost" >webcontrol_localhost</a> </td>
<td bgcolor="#edf4f9" ><a href="#webcontrol_html_output" >webcontrol_html_output</a> </td>
<td bgcolor="#edf4f9" ><a href="#webcontrol_authentication" >webcontrol_authentication</a> </td>
@@ -4825,6 +4831,23 @@ one camera, in which case it can be placed in motion.conf).
<p></p>
<h3><a name="substream_port"></a> substream_port </h3>
<p></p>
<ul>
<li> Type: Integer</li>
<li> Range / Valid values: 0 - 65535</li>
<li> Default: 0 (disabled)</li>
</ul>
<p></p>
# 50% scaled down substream (default: 0 = disabled)
TCP port on which motion will listen for incoming connections to substream.
Substream shares same settings as mini-http streaming server
Image is scaled down by 50% including timestamp
Substream can save bandwidth when viewing over mobile internet or poor wireless
<p></p>
<h3><a name="stream_quality"></a> stream_quality </h3>
<p></p>
<ul>
@@ -4934,6 +4957,7 @@ If the webcontrol page has HTML enabled, Motion displays all of the streams on t
format so that all the images can be viewed by standard browsers.
<p></p>
This parameter indicates the percentage to scale the stream image when it is placed on the page.
Note that this is scaled on the browser side, motion will keep sending full frame
Numbers greater than 100 are permitted.
<p></p>
<p></p>

View File

@@ -970,19 +970,17 @@ void overlay_largest_label(struct context *cnt, unsigned char *out)
*
* Returns the dest_image_size if successful. Otherwise 0.
*/
int put_picture_memory(struct context *cnt, unsigned char* dest_image, int image_size,
unsigned char *image, int quality)
int put_picture_memory(struct context *cnt, unsigned char* dest_image, int image_size, unsigned char *image,
int quality, int width, int height)
{
switch (cnt->imgs.type) {
case VIDEO_PALETTE_YUV420P:
return put_jpeg_yuv420p_memory(dest_image, image_size, image,
cnt->imgs.width, cnt->imgs.height, quality, cnt, &(cnt->current_image->timestamp_tv), &(cnt->current_image->location));
width, height, quality, cnt, &(cnt->current_image->timestamp_tv), &(cnt->current_image->location));
case VIDEO_PALETTE_GREY:
return put_jpeg_grey_memory(dest_image, image_size, image,
cnt->imgs.width, cnt->imgs.height, quality);
return put_jpeg_grey_memory(dest_image, image_size, image, width, height, quality);
default:
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "Unknown image type %d",
cnt->imgs.type);
MOTION_LOG(WRN, TYPE_ALL, NO_ERRNO, "Unknown image type %d", cnt->imgs.type);
}
return 0;
@@ -1232,3 +1230,31 @@ void preview_save(struct context *cnt)
cnt->current_image = saved_current_image;
}
}
/**
* scale_half_yuv420p
* scale down by half yuv420p
*
* Returns pointer to scaled image
*/
unsigned char *scale_half_yuv420p(int origwidth, int origheight, unsigned char *img)
{
/* allocate buffer for resized image */
unsigned char *scaled_img = mymalloc ((origwidth/2 * origheight/2) * 3 / 2);
int i = 0, x, y;
for (y = 0; y < origheight; y+=2)
for (x = 0; x < origwidth; x+=2)
scaled_img[i++] = img[y * origwidth + x];
for (y = 0; y < origheight / 2; y+=2)
for (x = 0; x < origwidth; x += 4)
{
scaled_img[i++] = img[(origwidth * origheight) + (y * origwidth) + x];
scaled_img[i++] = img[(origwidth * origheight) + (y * origwidth) + (x + 1)];
}
return scaled_img;
}

View File

@@ -17,9 +17,10 @@ void overlay_fixed_mask(struct context *, unsigned char *);
void put_fixed_mask(struct context *, const char *);
void overlay_largest_label(struct context *, unsigned char *);
void put_picture_fd(struct context *, FILE *, unsigned char *, int);
int put_picture_memory(struct context *, unsigned char*, int, unsigned char *, int);
int put_picture_memory(struct context *, unsigned char*, int, unsigned char *, int, int, int);
void put_picture(struct context *, char *, unsigned char *, int);
unsigned char *get_pgm(FILE *, int, int);
void preview_save(struct context *);
unsigned char *scale_half_yuv420p(int, int, unsigned char*);
#endif /* _INCLUDE_PICTURE_H_ */

View File

@@ -33,6 +33,8 @@
typedef void* (*auth_handler)(void*);
struct auth_param {
struct context *cnt;
struct stream *stm;
int *stream_count;
int sock;
int sock_flags;
int* thread_count;
@@ -233,8 +235,8 @@ static void* handle_basic_auth(void* param)
/* Lock the mutex */
pthread_mutex_lock(&stream_auth_mutex);
stream_add_client(&p->cnt->stream, p->sock);
p->cnt->stream_count++;
stream_add_client(p->stm, p->sock);
(*p->stream_count)++;
p->thread_count--;
/* Unlock the mutex */
@@ -596,8 +598,8 @@ Error:
/* Lock the mutex */
pthread_mutex_lock(&stream_auth_mutex);
stream_add_client(&p->cnt->stream, p->sock);
p->cnt->stream_count++;
stream_add_client(p->stm, p->sock);
(*p->stream_count)++;
p->thread_count--;
/* Unlock the mutex */
@@ -629,7 +631,7 @@ Invalid_Request:
*
*
*/
static void do_client_auth(struct context *cnt, int sc)
static void do_client_auth(struct context *cnt, struct stream *stm, int *stream_count, int sc)
{
pthread_t thread_id;
pthread_attr_t attr;
@@ -661,6 +663,8 @@ static void do_client_auth(struct context *cnt, int sc)
handle_param = mymalloc(sizeof(struct auth_param));
handle_param->cnt = cnt;
handle_param->stm = stm;
handle_param->stream_count = stream_count;
handle_param->sock = sc;
handle_param->conf = &cnt->conf;
handle_param->thread_count = &thread_count;
@@ -1032,13 +1036,13 @@ static int stream_check_write(struct stream *list)
*
* Returns: stream socket descriptor.
*/
int stream_init(struct context *cnt)
int stream_init(struct stream *stm, int stream_port, int stream_localhost, int ipv6_enabled)
{
cnt->stream.socket = http_bindsock(cnt->conf.stream_port, cnt->conf.stream_localhost,
cnt->conf.ipv6_enabled);
cnt->stream.next = NULL;
cnt->stream.prev = NULL;
return cnt->stream.socket;
stm->socket = http_bindsock(stream_port, stream_localhost, ipv6_enabled);
stm->next = NULL;
stm->prev = NULL;
return stm->socket;
}
/**
@@ -1046,16 +1050,17 @@ int stream_init(struct context *cnt)
* This function is called from the motion_loop when it ends
* and motion is terminated or restarted.
*/
void stream_stop(struct context *cnt)
void stream_stop(struct stream *stm)
{
struct stream *list;
struct stream *next = cnt->stream.next;
struct stream *next = stm->next;
/* TODO friendly info which socket is closing */
MOTION_LOG(NTC, TYPE_STREAM, NO_ERRNO, "Closing motion-stream listen socket"
" & active motion-stream sockets");
close(cnt->stream.socket);
cnt->stream.socket = -1;
close(stm->socket);
stm->socket = -1;
while (next) {
list = next;
@@ -1090,12 +1095,13 @@ void stream_stop(struct context *cnt)
* Note: Clients that have disconnected are handled in the stream_flush()
* function.
*/
void stream_put(struct context *cnt, unsigned char *image)
void stream_put(struct context *cnt, struct stream *stm, int *stream_count, unsigned char *image,
int do_scale_down)
{
struct timeval timeout;
struct stream_buffer *tmpbuffer;
fd_set fdread;
int sl = cnt->stream.socket;
int sl = stm->socket;
int sc;
/* Tthe following string has an extra 16 chars at end for length. */
const char jpeghead[] = "--BoundaryString\r\n"
@@ -1104,6 +1110,10 @@ void stream_put(struct context *cnt, unsigned char *image)
int headlength = sizeof(jpeghead) - 1; /* Don't include terminator. */
char len[20]; /* Will be used for sprintf, must be >= 16 */
/* will point either to the original image or a scaled down */
unsigned char *img = image;
int image_width = cnt->imgs.width, image_height = cnt->imgs.height, image_size = cnt->imgs.size;
/*
* Timeout struct used to timeout the time we wait for a client
* and we do not wait at all.
@@ -1111,7 +1121,7 @@ void stream_put(struct context *cnt, unsigned char *image)
timeout.tv_sec = 0;
timeout.tv_usec = 0;
FD_ZERO(&fdread);
FD_SET(cnt->stream.socket, &fdread);
FD_SET(stm->socket, &fdread);
/*
* If we have not reached the max number of allowed clients per
@@ -1120,27 +1130,48 @@ void stream_put(struct context *cnt, unsigned char *image)
* add this to the end of the chain of stream structs that are linked
* to each other.
*/
if ((cnt->stream_count < DEF_MAXSTREAMS) &&
if ((*stream_count < DEF_MAXSTREAMS) &&
(select(sl + 1, &fdread, NULL, NULL, &timeout) > 0)) {
sc = http_acceptsock(sl);
if (cnt->conf.stream_auth_method == 0) {
stream_add_client(&cnt->stream, sc);
cnt->stream_count++;
stream_add_client(stm, sc);
(*stream_count)++;
} else {
do_client_auth(cnt, sc);
do_client_auth(cnt, stm, stream_count, sc);
}
}
/* if there is no connected clients - nothing to do, return */
if (*stream_count <= 0)
return;
/* substream put - scale image down and update pointer to the scaled buffer */
if (do_scale_down)
{
/* TODO for now just scale 50%, better resize image to a config predefined size */
int origwidth = cnt->imgs.width, origheight = cnt->imgs.height;
int subwidth = origwidth/2, subheight = origheight/2;
int subsize = subwidth * subheight * 3 / 2;
/* allocate new buffer and scale image */
img = scale_half_yuv420p (origwidth, origheight, img);
image_width = subwidth;
image_height = subheight;
image_size = subsize;
}
/* Lock the mutex */
if (cnt->conf.stream_auth_method != 0)
pthread_mutex_lock(&stream_auth_mutex);
/* Call flush to send any previous partial-sends which are waiting. */
stream_flush(&cnt->stream, &cnt->stream_count, cnt->conf.stream_limit);
stream_flush(stm, stream_count, cnt->conf.stream_limit);
/* Check if any clients have available buffers. */
if (stream_check_write(&cnt->stream)) {
if (stream_check_write(stm)) {
/*
* Yes - create a new tmpbuffer for current image.
* Note that this should create a buffer which is *much* larger
@@ -1172,8 +1203,8 @@ void stream_put(struct context *cnt, unsigned char *image)
wptr += headlength;
/* Create a jpeg image and place into tmpbuffer. */
tmpbuffer->size = put_picture_memory(cnt, wptr, cnt->imgs.size, image,
cnt->conf.stream_quality);
tmpbuffer->size = put_picture_memory(cnt, wptr, image_size, img,
cnt->conf.stream_quality, image_width, image_height);
/* Fill in the image length into the header. */
imgsize = sprintf(len, "%9ld\r\n\r\n", tmpbuffer->size);
@@ -1193,7 +1224,7 @@ void stream_put(struct context *cnt, unsigned char *image)
* And finally put this buffer to all clients with
* no outstanding data from previous frames.
*/
stream_add_write(&cnt->stream, tmpbuffer, cnt->conf.stream_maxrate);
stream_add_write(stm, tmpbuffer, cnt->conf.stream_maxrate);
} else {
MOTION_LOG(ERR, TYPE_STREAM, SHOW_ERRNO, "Error creating tmpbuffer");
}
@@ -1203,11 +1234,17 @@ void stream_put(struct context *cnt, unsigned char *image)
* Now we call flush again. This time (assuming some clients were
* ready for the new frame) the new data will be written out.
*/
stream_flush(&cnt->stream, &cnt->stream_count, cnt->conf.stream_limit);
stream_flush(stm, stream_count, cnt->conf.stream_limit);
/* Unlock the mutex */
if (cnt->conf.stream_auth_method != 0)
pthread_mutex_unlock(&stream_auth_mutex);
/* free resized image buffer */
if (do_scale_down)
{
free (img);
}
return;
}

View File

@@ -38,8 +38,8 @@ struct stream {
struct stream *next;
};
int stream_init(struct context *);
void stream_put(struct context *, unsigned char *);
void stream_stop(struct context *);
int stream_init(struct stream *, int, int, int);
void stream_put(struct context *, struct stream *, int *, unsigned char *, int);
void stream_stop(struct stream *);
#endif /* _INCLUDE_STREAM_H_ */

View File

@@ -2118,7 +2118,7 @@ static unsigned int handle_get(int client_socket, const char *url, void *userdat
sprintf(res, "<a href=http://%s:%d> "
"<img src=http://%s:%d/ border=0 width=%d%%></a>\n"
,httphostname,cnt[y]->conf.stream_port
,httphostname,cnt[y]->conf.stream_port
,httphostname,(cnt[y]->conf.substream_port)?cnt[y]->conf.substream_port:cnt[y]->conf.stream_port
,cnt[y]->conf.stream_preview_scale);
send_template(client_socket, res);
}