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 with groundsdk-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