User Guide¶
Use the PDrAW program¶
The PDrAW program can be used to display a live stream from an Anafi, connected directly with wifi or through a SkyController.
Note
For macOS users, replace the following parts of example command lines:
groundsdk-linux
withgroundsdk-macos
# Run from the workspace root directory
$ ./out/groundsdk-linux/staging/native-wrapper.sh pdraw -u rtsp://192.168.42.1/live
# Or rtsp://192.168.53.1/live if connected through a SkyController
It can also be used to replay a file from the drone memory/SD-card
# Run from the workspace root directory
$ ./out/groundsdk-linux/staging/native-wrapper.sh pdraw -u rtsp://192.168.42.1/replay/100000010001.MP4
# Or rtsp://192.168.53.1/replay/XXXX if connected through a SkyController
# The replay URL can be queried from the drone webserver:
# http://anafi.local for Anafi or http://anafi-ai.local for Anafi Ai
Or play a local file
# Run from the workspace root directory
$ ./out/groundsdk-linux/staging/native-wrapper.sh pdraw -u path/to/file.mp4
# No need to be connected to a drone!
Further options can be found with the -h
flag, e.g.:
--hud
adds an overlayed display on the video, computed from the metadata--zebras
enables overexposure zebra computation
Build your own app with libpdraw-backend¶
To build an application using libpdraw-backend, you can either use alchemy to build your app, or use your own build system.
Note
The same applies when using libpdraw directly, but your app will be responsible for providing and running the pomp_loop event loop.
Using Alchemy to create a new executable¶
Alchemy searches for modules in the packages
directory of your SDK
workspace. For this example, we will create an application in the
<SDK>/packages/test-app
directory.
First, create an atom.mk
file in this directory. This file tells Alchemy
what to build, and which dependencies are required:
LOCAL_PATH := $(call my-dir)
include $(CLEAR_VARS)
# LOCAL_MODULE is the name of your app.
LOCAL_MODULE := test-app
# LOCAL_LIBRARIES is the list of libraries your app depends on.
LOCAL_LIBRARIES := libpdraw-backend
# LOCAL_SRC_FILES is the list of source files in your app.
LOCAL_SRC_FILES := main.c
include $(BUILD_EXECUTABLE)
Then create a main.c
file. The provided example creates a pdraw-backend
instance, opens the given url (or local path), then closes the instance.
It implements the very basic operation of libpdraw-backend, and is sufficient
to check if the binary is properly linked.
1#include <pdraw/pdraw_backend.h>
2
3#include <pthread.h>
4#include <stdio.h>
5#include <string.h>
6
7struct ctx {
8 pthread_mutex_t mutex;
9 pthread_cond_t cond;
10 int completed;
11 int status;
12};
13
14static void ctx_signal_resp(struct ctx *ctx, int status)
15{
16 pthread_mutex_lock(&ctx->mutex);
17 ctx->status = status;
18 ctx->completed = 1;
19 pthread_cond_signal(&ctx->cond);
20 pthread_mutex_unlock(&ctx->mutex);
21}
22
23static int ctx_wait_resp(struct ctx *ctx)
24{
25 int status;
26
27 pthread_mutex_lock(&ctx->mutex);
28 while (!ctx->completed)
29 pthread_cond_wait(&ctx->cond, &ctx->mutex);
30 status = ctx->status;
31 ctx->completed = 0;
32 pthread_mutex_unlock(&ctx->mutex);
33
34 return status;
35}
36
37static void
38pdraw_stop_resp(struct pdraw_backend *pdraw, int status, void *userdata)
39{
40 struct ctx *ctx = userdata;
41
42 printf("pdraw_stop_resp: %d (%s)\n", status, strerror(-status));
43
44 ctx_signal_resp(ctx, status);
45}
46
47static void pdraw_demuxer_open_resp(struct pdraw_backend *pdraw,
48 struct pdraw_demuxer *demuxer,
49 int status,
50 void *userdata)
51{
52 struct ctx *ctx = userdata;
53
54 printf("pdraw_demuxer_open_resp: %d (%s)\n", status, strerror(-status));
55
56 ctx_signal_resp(ctx, status);
57}
58
59static void pdraw_demuxer_close_resp(struct pdraw_backend *pdraw,
60 struct pdraw_demuxer *demuxer,
61 int status,
62 void *userdata)
63{
64 struct ctx *ctx = userdata;
65
66 printf("pdraw_demuxer_close_resp: %d (%s)\n",
67 status,
68 strerror(-status));
69
70 ctx_signal_resp(ctx, status);
71}
72
73static const struct pdraw_backend_cbs backend_cbs = {
74 .stop_resp = pdraw_stop_resp,
75};
76
77static const struct pdraw_backend_demuxer_cbs demuxer_cbs = {
78 .open_resp = pdraw_demuxer_open_resp,
79 .close_resp = pdraw_demuxer_close_resp,
80};
81
82int main(int argc, char *argv[])
83{
84 struct pdraw_backend *pdraw = NULL;
85 struct pdraw_demuxer *demuxer = NULL;
86 struct ctx ctx = {
87 .mutex = PTHREAD_MUTEX_INITIALIZER,
88 .cond = PTHREAD_COND_INITIALIZER,
89 };
90 int ret;
91
92 if (argc < 2) {
93 fprintf(stderr, "Missing argument\n");
94 return 1;
95 }
96
97 ret = pdraw_be_new(&backend_cbs, &ctx, &pdraw);
98 if (ret != 0) {
99 fprintf(stderr, "pdraw_be_new: %d (%s)\n", ret, strerror(-ret));
100 goto exit;
101 }
102
103 ret = pdraw_be_demuxer_new_from_url(
104 pdraw, argv[1], &demuxer_cbs, &ctx, &demuxer);
105 if (ret != 0) {
106 fprintf(stderr,
107 "pdraw_be_demuxer_new_from_url: %d (%s)\n",
108 ret,
109 strerror(-ret));
110 goto exit;
111 }
112
113 /* Wait for demuxer open resp */
114 if (ctx_wait_resp(&ctx) != 0)
115 goto exit;
116
117 /* The demuxer is now open. This is the point where your app should go
118 * in a waiting state while the video is running. The demuxer will call
119 * its `ready_to_play' callback when it is safe to call
120 * `pdraw_be_demuxer_play()' to start receiving frames */
121
122 ret = pdraw_be_demuxer_close(pdraw, demuxer);
123 if (ret != 0) {
124 fprintf(stderr,
125 "pdraw_be_demuxer_close: %d (%s)\n",
126 ret,
127 strerror(-ret));
128 goto exit;
129 }
130
131 /* Wait for demuxer close resp */
132 ctx_wait_resp(&ctx);
133
134 /* Destroy the demuxer before stopping the backend */
135 pdraw_be_demuxer_destroy(pdraw, demuxer);
136
137 ret = pdraw_be_stop(pdraw);
138 if (ret != 0) {
139 fprintf(stderr,
140 "pdraw_be_stop: %d (%s)\n",
141 ret,
142 strerror(-ret));
143 goto exit;
144 }
145
146 /* Wait for stop resp */
147 ctx_wait_resp(&ctx);
148
149exit:
150 if (pdraw != NULL)
151 pdraw_be_destroy(pdraw);
152 pthread_mutex_destroy(&ctx.mutex);
153 pthread_cond_destroy(&ctx.cond);
154 return ret;
155}
The application can then be built with the following command:
# Run from the workspace root directory
$ ./build.sh -p groundsdk-linux -A test-app -j/1
And run with the following command:
# Run from the workspace root directory
$ ./out/groundsdk-linux/staging/native-wrapper.sh test-app rtsp://192.168.42.1/live
Using a custom build system with the SDK libraries¶
If you don’t want to use Alchemy to build your app, you can directly use the output files from the Alchemy build (libraries and headers) from any other build system.
First, we need to build the SDK from Alchemy to be used externally:
# Run from the workspace root directory
$ ./build.sh -p groundsdk-linux -A sdk -j/1
For this example, we will use the same main file from the Alchemy sample,
and assume that the SDK was properly built from a folder called <SDK>
.
The following makefile can be used to build the same binary as before, but
outside of the Alchemy build system:
BIN := test-app
SRC := main.c
OBJ := $(SRC:.c=.o)
# Change this to point to your SDK dir
SDK_DIR := ../../../..
SDK_INC_DIR := $(SDK_DIR)/out/groundsdk-linux/sdk/usr/include
SDK_LIB_DIR := $(SDK_DIR)/out/groundsdk-linux/sdk/usr/lib
SDK_PROTOBUF_INC_DIR := $(SDK_INC_DIR)/libvideo-metadata-protobuf/generated
all: $(BIN)
$(BIN): $(OBJ)
@gcc -o $@ -L$(SDK_LIB_DIR) -Wl,-rpath=$(SDK_LIB_DIR) $^ -lpdraw-backend
%.o: %.c
@gcc -c -o $@ -I$(SDK_INC_DIR) -I$(SDK_PROTOBUF_INC_DIR) $<
clean:
@rm -f $(OBJ) $(BIN)
.PHONY: clean
As the libraries from Alchemy are not installed in the system directories, you have to provide the library path to the loader when running the result binary:
# Run from your Makefile/main.c directory
$ make
$ env LD_LIBRARY_PATH=<SDK>/out/groundsdk-linux/sdk/usr/lib ./test-app rtsp://192.168.42.1/live