diff --git a/conf/airframes/examples/bebop.xml b/conf/airframes/examples/bebop.xml index c55455bfb9..c5b49a3699 100644 --- a/conf/airframes/examples/bebop.xml +++ b/conf/airframes/examples/bebop.xml @@ -17,7 +17,9 @@ - + + + @@ -44,9 +46,13 @@ - + + + + + diff --git a/conf/control_panel_example.xml b/conf/control_panel_example.xml index 35a0d7c64c..cc568eba80 100644 --- a/conf/control_panel_example.xml +++ b/conf/control_panel_example.xml @@ -68,6 +68,11 @@ + + + + +
diff --git a/conf/modules/video_rtp_stream.xml b/conf/modules/video_rtp_stream.xml index eca8a76c47..45e06e75fb 100644 --- a/conf/modules/video_rtp_stream.xml +++ b/conf/modules/video_rtp_stream.xml @@ -13,9 +13,11 @@ + + @@ -47,10 +49,11 @@ VIEWVIDEO_HOST ?= $(MODEM_HOST) VIEWVIDEO_PORT_OUT ?= 5000 + VIEWVIDEO_PORT2_OUT ?= 6000 VIEWVIDEO_BROADCAST ?= TRUE VIEWVIDEO_USE_NETCAT ?= FALSE - VIEWVID_CFLAGS = -DVIEWVIDEO_HOST=$(VIEWVIDEO_HOST) -DVIEWVIDEO_PORT_OUT=$(VIEWVIDEO_PORT_OUT) + VIEWVID_CFLAGS = -DVIEWVIDEO_HOST=$(VIEWVIDEO_HOST) -DVIEWVIDEO_PORT_OUT=$(VIEWVIDEO_PORT_OUT) -DVIEWVIDEO_PORT2_OUT=$(VIEWVIDEO_PORT2_OUT) ifneq (,$(findstring $(VIEWVIDEO_USE_NETCAT),0 FALSE)) ap.CFLAGS += $(VIEWVID_CFLAGS) -DVIEWVIDEO_BROADCAST=$(VIEWVIDEO_BROADCAST) nps.CFLAGS += $(VIEWVID_CFLAGS) -DVIEWVIDEO_BROADCAST=FALSE diff --git a/sw/airborne/modules/computer_vision/cv.c b/sw/airborne/modules/computer_vision/cv.c index 1a4dcdae17..aec8bff7b2 100644 --- a/sw/airborne/modules/computer_vision/cv.c +++ b/sw/airborne/modules/computer_vision/cv.c @@ -175,7 +175,7 @@ void cv_run_device(struct video_config_t *device, struct image_t *img) } if (listener->async != NULL) { - // Send image to asynchronous thread, only update listener if succesful + // Send image to asynchronous thread, only update listener if successful if (!cv_async_function(listener->async, img)) { // Store timestamp listener->ts = img->ts; diff --git a/sw/airborne/modules/computer_vision/video_thread.c b/sw/airborne/modules/computer_vision/video_thread.c index 490d816309..c057009c16 100644 --- a/sw/airborne/modules/computer_vision/video_thread.c +++ b/sw/airborne/modules/computer_vision/video_thread.c @@ -131,7 +131,7 @@ static void *video_thread_function(void *data) if (dt_us < fps_period_us) { usleep(fps_period_us - dt_us); } else { - //fprintf(stderr, "[%s] desired %i fps, only managing %.1f fps\n", print_tag, vid->fps, 1000000.f / dt_us); + fprintf(stderr, "[%s] desired %i fps, only managing %.1f fps\n", print_tag, vid->fps, 1000000.f / dt_us); } } diff --git a/sw/airborne/modules/computer_vision/viewvideo.c b/sw/airborne/modules/computer_vision/viewvideo.c index 0a2cda0779..d50ed2918e 100644 --- a/sw/airborne/modules/computer_vision/viewvideo.c +++ b/sw/airborne/modules/computer_vision/viewvideo.c @@ -72,7 +72,7 @@ PRINT_CONFIG_VAR(VIEWVIDEO_RTP_TIME_INC) #ifdef VIDEO_THREAD_SHOT_PATH #define VIEWVIDEO_SHOT_PATH VIDEO_THREAD_SHOT_PATH #else -#define VIEWVIDEO_SHOT_PATH /data/video/images +#define VIEWVIDEO_SHOT_PATH /data/ftp/internal_000/images #endif #endif PRINT_CONFIG_VAR(VIEWVIDEO_SHOT_PATH) @@ -87,7 +87,7 @@ PRINT_CONFIG_VAR(VIEWVIDEO_FPS) #ifndef VIEWVIDEO_NICE_LEVEL #define VIEWVIDEO_NICE_LEVEL 5 #endif -PRINT_CONFIG_VAR(VIEWVIDEO_FPS) +PRINT_CONFIG_VAR(VIEWVIDEO_NICE_LEVEL) // Check if we are using netcat instead of RTP/UDP #ifndef VIEWVIDEO_USE_NETCAT @@ -102,7 +102,8 @@ PRINT_CONFIG_VAR(VIEWVIDEO_FPS) #include PRINT_CONFIG_MSG("[viewvideo] Using netcat.") #else -struct UdpSocket video_sock; +struct UdpSocket video_sock1; +struct UdpSocket video_sock2; PRINT_CONFIG_MSG("[viewvideo] Using RTP/UDP stream.") PRINT_CONFIG_VAR(VIEWVIDEO_USE_RTP) #endif @@ -110,6 +111,7 @@ PRINT_CONFIG_VAR(VIEWVIDEO_USE_RTP) /* These are defined with configure */ PRINT_CONFIG_VAR(VIEWVIDEO_HOST) PRINT_CONFIG_VAR(VIEWVIDEO_PORT_OUT) +PRINT_CONFIG_VAR(VIEWVIDEO_PORT2_OUT) // Initialize the viewvideo structure with the defaults struct viewvideo_t viewvideo = { @@ -121,12 +123,16 @@ struct viewvideo_t viewvideo = { #endif }; +#if defined(VIEWVIDEO_CAMERA) && defined(VIEWVIDEO_CAMERA2) && VIEWVIDEO_BROADCAST == true +#warning Broadcasting dual video stream causes too much udp overhead resulting in unstable streaming +#warning We recomment using a static VIEWVIDEO_HOST address and VIEWVIDEO_BROADCAST to false +#endif + /** * Handles all the video streaming and saving of the image shots - * This is a sepereate thread, so it needs to be thread safe! + * This is a separate thread, so it needs to be thread safe! */ -struct image_t *viewvideo_function(struct image_t *img); -struct image_t *viewvideo_function(struct image_t *img) +static struct image_t *viewvideo_function(struct UdpSocket *socket, struct image_t *img) { // Resize image if needed struct image_t img_small; @@ -178,10 +184,9 @@ struct image_t *viewvideo_function(struct image_t *img) } #else if (viewvideo.use_rtp) { - // Send image with RTP rtp_frame_send( - &video_sock, // UDP socket + socket, // UDP socket &img_jpeg, 0, // Format 422 VIEWVIDEO_QUALITY_FACTOR, // Jpeg-Quality @@ -207,6 +212,20 @@ struct image_t *viewvideo_function(struct image_t *img) return NULL; // No new images were created } +#ifdef VIEWVIDEO_CAMERA +static struct image_t *viewvideo_function1(struct image_t *img) +{ + return viewvideo_function(&video_sock1, img); +} +#endif + +#ifdef VIEWVIDEO_CAMERA2 +static struct image_t *viewvideo_function2(struct image_t *img) +{ + return viewvideo_function(&video_sock2, img); +} +#endif + /** * Initialize the view video */ @@ -214,9 +233,6 @@ void viewvideo_init(void) { char save_name[512]; - struct video_listener *listener = cv_add_to_device_async(&VIEWVIDEO_CAMERA, viewvideo_function, VIEWVIDEO_NICE_LEVEL); - listener->maximum_fps = VIEWVIDEO_FPS; - viewvideo.is_streaming = true; #if VIEWVIDEO_USE_NETCAT @@ -237,8 +253,21 @@ void viewvideo_init(void) } #else // Open udp socket - udp_socket_create(&video_sock, STRINGIFY(VIEWVIDEO_HOST), VIEWVIDEO_PORT_OUT, -1, VIEWVIDEO_BROADCAST); +#ifdef VIEWVIDEO_CAMERA1 + if (udp_socket_create(&video_sock1, STRINGIFY(VIEWVIDEO_HOST), VIEWVIDEO_PORT_OUT, -1, VIEWVIDEO_BROADCAST)) { + printf("[viewvideo]: failed to open view video socket, HOST=%s, port=%d\n", STRINGIFY(VIEWVIDEO_HOST), + VIEWVIDEO_PORT_OUT); + } +#endif +#ifdef VIEWVIDEO_CAMERA2 + if (udp_socket_create(&video_sock2, STRINGIFY(VIEWVIDEO_HOST), VIEWVIDEO_PORT2_OUT, -1, VIEWVIDEO_BROADCAST)) { + printf("[viewvideo]: failed to open view video socket, HOST=%s, port=%d\n", STRINGIFY(VIEWVIDEO_HOST), + VIEWVIDEO_PORT2_OUT); + } +#endif + + // todo: check what this is for! // Create an SDP file for the streaming sprintf(save_name, "%s/stream.sdp", STRINGIFY(VIEWVIDEO_SHOT_PATH)); FILE *fp = fopen(save_name, "w"); @@ -251,5 +280,18 @@ void viewvideo_init(void) printf("[viewvideo] Failed to create SDP file.\n"); } #endif -} +#ifdef VIEWVIDEO_CAMERA + struct video_listener *listener1 = cv_add_to_device_async(&VIEWVIDEO_CAMERA, viewvideo_function1, + VIEWVIDEO_NICE_LEVEL); + listener1->maximum_fps = VIEWVIDEO_FPS; + fprintf(stderr, "[viewvideo] Added asynchronous video streamer lister for CAMERA1\n"); +#endif + +#ifdef VIEWVIDEO_CAMERA2 + struct video_listener *listener2 = cv_add_to_device_async(&VIEWVIDEO_CAMERA2, viewvideo_function2, + VIEWVIDEO_NICE_LEVEL); + listener2->maximum_fps = VIEWVIDEO_FPS; + fprintf(stderr, "[viewvideo] Added asynchronous video streamer lister for CAMERA2\n"); +#endif +} diff --git a/sw/tools/rtp_viewer/rtp_stream.sdp b/sw/tools/rtp_viewer/rtp_5000.sdp similarity index 100% rename from sw/tools/rtp_viewer/rtp_stream.sdp rename to sw/tools/rtp_viewer/rtp_5000.sdp diff --git a/sw/tools/rtp_viewer/rtp_6000.sdp b/sw/tools/rtp_viewer/rtp_6000.sdp new file mode 100644 index 0000000000..0c59b59203 --- /dev/null +++ b/sw/tools/rtp_viewer/rtp_6000.sdp @@ -0,0 +1,3 @@ +v=0 +m=video 6000 RTP/AVP 26 +c=IN IP4 0.0.0.0 diff --git a/sw/tools/rtp_viewer/rtp_viewer.py b/sw/tools/rtp_viewer/rtp_viewer.py index b0a1f98294..d7593bbc46 100755 --- a/sw/tools/rtp_viewer/rtp_viewer.py +++ b/sw/tools/rtp_viewer/rtp_viewer.py @@ -2,6 +2,8 @@ import cv2 import sys +import getopt +import re from os import path, getenv PPRZ_SRC = getenv("PAPARAZZI_SRC", path.normpath(path.join(path.dirname(path.abspath(__file__)), '../../../'))) @@ -10,9 +12,41 @@ sys.path.append(PPRZ_SRC + "/sw/ext/pprzlink/lib/v1.0/python") from pprzlink.ivy import IvyMessagesInterface from pprzlink.message import PprzMessage +def Usage(scmd): + lpathitem = scmd.split('/') + fmt = '''Usage: %s [-h | --help] [-p PORT | --port=PORT] [-s SCALE | --scale=SCALE] [-r ROTATE | --rotate=ROTATE] [] +where +\t-h | --help print this message +\t-p PORT | --port=PORT where PORT is the port number to open for the RTP stream (5000 or 6000) +\t-s SCALE | --scale=SCALE where SCALE is the scaling factor to apply to the incoming video stream (default: 1) +\t-r ROTATE | --rotate=ROTATE where ROTATE is the number of clockwise 90deg rotations to apply to the stream [0-3] (default: 0) +''' + print(fmt % lpathitem[-1]) + +def GetOptions(): + options = {'port':[], 'scale':[], 'rotate':[]} + try: + optlist, left_args = getopt.getopt(sys.argv[1:],'h:p:s:r:', ['help', 'port=', 'scale=', 'rotate=']) + except getopt.GetoptError: + # print help information and exit: + Usage(sys.argv[0]) + sys.exit(2) + for o, a in optlist: + if o in ("-h", "--help"): + Usage(sys.argv[0]) + sys.exit() + elif o in ("-p", "--port"): + options['port'].append(int(a)) + elif o in ("-s", "--scale"): + options['scale'].append(float(a)) + elif o in ("-r", "--rotate"): + options['rotate'].append(int(a)) + return options + class RtpViewer: running = False + scale = 1 rotate = 0 frame = None mouse = dict() @@ -60,6 +94,10 @@ class RtpViewer: # Draw a rectangle indicating the region of interest cv2.rectangle(self.frame, self.mouse['start'], self.mouse['now'], (0, 255, 0), 2) + if self.scale != 1: + h, w = self.frame.shape[:2] + self.frame = cv2.resize(self.frame, (int(self.scale * w), int(self.scale * h))) + # Show the image in a window cv2.imshow('rtp', self.frame) @@ -72,7 +110,7 @@ class RtpViewer: self.mouse['start'] = None def on_mouse(self, event, x, y, flags, param): - if event == cv2.EVENT_LBUTTONDOWN and self.rotate == 0: + if event == cv2.EVENT_LBUTTONDOWN and self.rotate == 0 and False: self.mouse['start'] = (x, y) if event == cv2.EVENT_RBUTTONDOWN: @@ -110,11 +148,31 @@ class RtpViewer: if __name__ == '__main__': - viewer = RtpViewer("rtp_stream.sdp") + import sys + import os + + options = GetOptions() + if not options['port']: + Usage(sys.argv[0]) + filename = os.path.dirname(os.path.abspath(__file__)) + "/rtp_" + str(options['port'][0]) + ".sdp" + print(filename) + + #set defaults + if not options['scale']: + options['scale'][0] = 1. + if not options['rotate']: + options['rotate'][0] = 0 + + print(options['scale'][0]) + print(options['rotate'][0]) + + viewer = RtpViewer(filename) + viewer.scale = options['scale'][0] + viewer.rotate = options['rotate'][0] if not viewer.cap.isOpened(): viewer.cleanup() - sys.exit("Can't open video stream") + raise IOError("Can't open video stream") viewer.run() viewer.cleanup()