├── run.sh ├── .gitignore ├── .gitmodules ├── CMakeLists.txt ├── LICENSE ├── README.md └── src └── main.cpp /run.sh: -------------------------------------------------------------------------------- 1 | #/bin/bash 2 | rm out.mp4 3 | ./build-default/src/run 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | *.swp 2 | *.swo 3 | build 4 | *.log 5 | *.mp4 6 | *.bin 7 | *.MP4 8 | *.xml 9 | *~ 10 | massif.* 11 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "external/gpmf-write"] 2 | path = external/gpmf-write 3 | url = https://github.com/gopro/gpmf-write.git 4 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | # CMakeLists.txt 2 | cmake_minimum_required (VERSION 3.5.1) 3 | project (mp4-inject-sensor-data) 4 | 5 | set(CMAKE_C_COMPILER "gcc") 6 | set(CMAKE_CXX_COMPILER "g++") 7 | set(CMAKE_CXX_FLAGS "${CMAKE_C_FLAGS} -O2 -fPIC -g -Wno-write-strings") 8 | 9 | find_package(PkgConfig REQUIRED) 10 | pkg_check_modules(GST REQUIRED gstreamer-1.0>=1.4) 11 | 12 | include_directories(${GST_INCLUDE_DIRS}) 13 | include_directories(external) 14 | 15 | link_libraries(${GST_LIBRARIES}) 16 | 17 | file(GLOB SOURCES src/*.c* external/*/*.c) 18 | 19 | add_executable(mp4-inject-sensor-data ${SOURCES}) 20 | 21 | target_link_libraries(mp4-inject-sensor-data ${GST_LIBRARIES}) 22 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2019 Blueye Robotics AS 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # mp4-inject-sensor-data 2 | Proof of concept for injecting sensor data into mp4 videos using gopros [gpmf](https://github.com/gopro/gpmf-parser) standard and a modified version of gstreamers [mp4mux](https://gstreamer.freedesktop.org/data/doc/gstreamer/head/gst-plugins-good/html/gst-plugins-good-plugins-mp4mux.html) element which is implemented [here](https://gitlab.freedesktop.org/erlend_ne/gst-plugins-good/tree/mp4mux-add-gpmf-track-latest). 3 | 4 | Example output from Quik Desktop after adding Gauges: https://youtu.be/wCWc-pXQkZE 5 | 6 | ## Build instructions 7 | 8 | First make sure to clone the repository with the gpmf-parse submodule, either by running: 9 | ``` 10 | git clone https://github.com/BluEye-Robotics/mp4-inject-sensor-data.git --recursive 11 | ``` 12 | or 13 | ``` 14 | git clone https://github.com/BluEye-Robotics/mp4-inject-sensor-data.git 15 | cd mp4-inject-sensor-data 16 | git submodule init 17 | git submodule update --recursive 18 | ``` 19 | 20 | Then you make a build directory and invoke cmake the normal way: 21 | ``` 22 | mkdir build 23 | cd build 24 | cmake .. 25 | make 26 | ``` 27 | 28 | The output binary will be called "mp4-inject-sensor-data", and it will create a video with a gpmf-track. 29 | This is provided that you use the custom version of gstreamer mentioned above. 30 | -------------------------------------------------------------------------------- /src/main.cpp: -------------------------------------------------------------------------------- 1 | // Copyright 2018 Blueye Robotics AS 2 | #include 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include "sys/time.h" 11 | #include "gpmf-write/GPMF_common.h" 12 | #include "gpmf-write/GPMF_writer.h" 13 | #include "cmath" 14 | 15 | GMainLoop *loop = NULL; 16 | 17 | 18 | GstElement *pipeline; 19 | GstElement *mp4mux, *filesink; 20 | GstElement *appsrc; 21 | 22 | guint sourceid = 0; /* To control the GSource */ 23 | 24 | uint64_t recording_beginning; 25 | 26 | #define SAMPLE_RATE 1 /* Samples per second we are sending */ 27 | 28 | inline uint64_t msNow(){ 29 | struct timeval tp; 30 | gettimeofday(&tp, NULL); 31 | 32 | uint64_t ms = tp.tv_sec*1000 + tp.tv_usec/1000; 33 | return ms; 34 | } 35 | 36 | #define sprintf_s(a,b,c) sprintf(a,c) 37 | 38 | #pragma pack(push) 39 | #pragma pack(1) //GPMF sensor data structures are always byte packed. 40 | 41 | size_t gpmfhandle = 0; 42 | size_t handleACCL; 43 | size_t handleGYRO; 44 | size_t handleGPS; 45 | size_t handleISOG; 46 | //size_t handleSHUT; 47 | char buffer[8192]; 48 | uint32_t *payload, payload_size; 49 | 50 | #pragma pack(pop) 51 | 52 | static gboolean push_data (gpointer data) { 53 | auto now = msNow(); 54 | static auto before = now; 55 | if (now - before < 1000) 56 | return TRUE; 57 | before = now; 58 | size_t queued; 59 | //g_object_get(appsrc, "current-level-bytes", &queued, NULL); 60 | //g_print("i%d", queued); 61 | g_print("i"); 62 | GstBuffer *gbuffer; 63 | GstFlowReturn ret; 64 | 65 | /* Create a new empty buffer */ 66 | gbuffer = gst_buffer_new(); 67 | 68 | //gst_buffer_fill(gbuffer, 0, (gpointer)&((*buf)[0]), buf->size()); 69 | 70 | //char b[4]; 71 | //static uint64_t counter = 0; 72 | ////g_print("%d", counter); 73 | ////snprintf(b, 10, "ii%d", counter++); 74 | //b[0] = 0x08; 75 | //b[1] = 0x08; 76 | //b[2] = 0x08; 77 | //b[3] = counter++ % 0xff; 78 | //GstMemory *mem = gst_allocator_alloc(NULL, strlen(b), NULL); 79 | //gst_buffer_append_memory(gbuffer, mem); 80 | //gst_buffer_fill(gbuffer, 0, b, strlen(b)); 81 | 82 | static uint32_t count = 0; 83 | ++count; 84 | uint32_t err; 85 | 86 | int16_t acc[200*3]; 87 | for (int16_t i = 0; i < 200*3; i+=3) 88 | { 89 | acc[i] = 4078; 90 | acc[i+1] = -20; 91 | acc[i+2] = 536; 92 | } 93 | err = GPMFWriteStreamStore(handleACCL, STR2FOURCC("ACCL"), 's', 3*sizeof(int16_t), 200, acc, GPMF_FLAGS_NONE); 94 | if (err) printf("err = %d\n", err); 95 | 96 | int16_t gyro[399*3]; 97 | for (int16_t i = 0; i < 399*3; i+=3) 98 | { 99 | gyro[i] = 1811; 100 | gyro[i+1] = 136; 101 | gyro[i+2] = 181; 102 | } 103 | err = GPMFWriteStreamStore(handleGYRO, STR2FOURCC("GYRO"), 's', 3*sizeof(int16_t), 399, gyro, GPMF_FLAGS_NONE); 104 | if (err) printf("err = %d\n", err); 105 | 106 | static int32_t lat = 406450466; 107 | static int32_t lng = -740687085; 108 | static int32_t alt = -33035; 109 | static float angle = 0; 110 | static uint32_t radius = 1; 111 | //if (count % 64 < 32) 112 | //{ 113 | // ++lat; 114 | // ++lng; 115 | //} 116 | //else 117 | //{ 118 | // --lat; 119 | // --lng; 120 | //} 121 | char utcdata[17]; 122 | static uint8_t mins = 0; 123 | uint8_t secs = count % 60; 124 | if (secs == 0) 125 | ++mins; 126 | snprintf(utcdata, 17, "17062012%02d%02d.515", mins, secs); 127 | GPMFWriteStreamStore(handleGPS, STR2FOURCC("GPSU"), 'c', 16*sizeof(char), 1, &utcdata, GPMF_FLAGS_STICKY); 128 | 129 | int32_t gps[18*5]; 130 | for (uint32_t i = 0; i < 18*5; i+=5) 131 | { 132 | radius += 1; 133 | angle += .1; 134 | 135 | lat = 406450466 + radius * cos(angle); 136 | lng = -740687085 + radius * sin(angle); 137 | alt-=100; 138 | 139 | gps[i] = lat; 140 | gps[i+1] = lng; 141 | gps[i+2] = alt; 142 | gps[i+3] = 15; 143 | gps[i+4] = 50; 144 | } 145 | err = GPMFWriteStreamStore(handleGPS, STR2FOURCC("GPS5"), 'l', 5*sizeof(int32_t), 18, &gps, GPMF_FLAGS_NONE); 146 | if (err) printf("err = %d\n", err); 147 | 148 | //float isog[4]; 149 | //for (uint32_t i = 0; i < 4; i++) 150 | //{ 151 | // isog[i] = 1.414; 152 | //} 153 | //err = GPMFWriteStreamStore(handleISOG, STR2FOURCC("ISOG"), 'f', 1*sizeof(float), 4, &isog, GPMF_FLAGS_NONE); 154 | //if (err) printf("err = %d\n", err); 155 | 156 | //float shut[4]; 157 | //for (uint32_t i = 0; i < 4; i++) 158 | //{ 159 | // shut[i] = 0; 160 | //} 161 | //err = GPMFWriteStreamStore(handleSHUT, STR2FOURCC("SHUT"), 'f', 1*sizeof(float), 4, &shut, GPMF_FLAGS_NONE); 162 | //if (err) printf("err = %d\n", err); 163 | 164 | //device_metacc* p = (device_metacc*)handleACCL; 165 | //GstMemory *mem = gst_allocator_alloc(NULL, p->payload_curr_size, NULL); 166 | //gst_buffer_append_memory(gbuffer, mem); 167 | //gst_buffer_fill(buffer, 0, p->payload_buffer, p->payload_alloc_size); //payload_curr_size); 168 | 169 | GPMFWriteGetPayload(gpmfhandle, GPMF_CHANNEL_TIMED, (uint32_t *)buffer, sizeof(buffer), &payload, &payload_size); 170 | 171 | //printf("payload_size = %d\n", payload_size); 172 | 173 | ////Using the GPMF_Parser, output some of the contents 174 | //GPMF_stream gs; 175 | //if (GPMF_OK == GPMF_Init(&gs, payload, payload_size)) 176 | //{ 177 | // GPMF_ResetState(&gs); 178 | // do 179 | // { 180 | // PrintGPMF(&gs); // printf current GPMF KLV 181 | // } while (GPMF_OK == GPMF_Next(&gs, GPMF_RECURSE_LEVELS)); 182 | //} 183 | //printf("\n"); 184 | 185 | GstMemory *mem = gst_allocator_alloc(NULL, payload_size, NULL); 186 | gst_buffer_append_memory(gbuffer, mem); 187 | gst_buffer_fill(gbuffer, 0, payload, payload_size); //payload_curr_size); 188 | 189 | //static GstClockTime timestamp = 0; 190 | GstClockTime timestamp = now - recording_beginning; 191 | timestamp *= 1e6; 192 | GST_BUFFER_PTS (gbuffer) = timestamp; 193 | //GST_BUFFER_DURATION (gbuffer) = GST_CLOCK_TIME_NONE; 194 | GST_BUFFER_DURATION (gbuffer) = gst_util_uint64_scale_int (1, GST_SECOND, SAMPLE_RATE); 195 | //timestamp += GST_BUFFER_DURATION (gbuffer); 196 | //g_print("\t%lu\t", timestamp); 197 | 198 | /* Push the buffer into the appsrc */ 199 | g_signal_emit_by_name (appsrc, "push-buffer", gbuffer, &ret); 200 | 201 | /* Free the buffer now that we are done with it */ 202 | gst_buffer_unref (gbuffer); 203 | 204 | if (ret != GST_FLOW_OK) { 205 | /* We got some error, stop sending data */ 206 | return FALSE; 207 | } 208 | 209 | return TRUE; 210 | } 211 | 212 | static void start_feed (GstElement *source, guint size, gpointer throwaway) { 213 | if (sourceid == 0) { 214 | g_print ("Start feeding\n"); 215 | sourceid = g_idle_add ((GSourceFunc) push_data, NULL); 216 | } 217 | } 218 | 219 | static void stop_feed (GstElement *source, gpointer throwaway) { 220 | if (sourceid != 0) { 221 | g_print ("Stop feeding\n"); 222 | g_source_remove (sourceid); 223 | sourceid = 0; 224 | } 225 | } 226 | 227 | void shutdown(int signum) 228 | { 229 | g_print("exit(%d)\n", signum); 230 | g_source_remove (sourceid); 231 | gst_element_send_event(pipeline, gst_event_new_eos()); 232 | GPMFWriteStreamClose(handleACCL); 233 | GPMFWriteStreamClose(handleGYRO); 234 | GPMFWriteStreamClose(handleGPS); 235 | GPMFWriteServiceClose(gpmfhandle); 236 | //exit(signum); 237 | } 238 | 239 | static gboolean bus_message (GstBus * bus, GstMessage * msg, gpointer user_data) 240 | { 241 | /* Parse message */ 242 | if (msg != NULL) { 243 | GError *err; 244 | gchar *debug_info; 245 | 246 | switch (GST_MESSAGE_TYPE (msg)) 247 | { 248 | case GST_MESSAGE_ERROR: 249 | gst_message_parse_error (msg, &err, &debug_info); 250 | g_printerr ("Error received from element %s: %s\n", GST_OBJECT_NAME (msg->src), err->message); 251 | g_printerr ("Debugging information: %s\n", debug_info ? debug_info : "none"); 252 | g_clear_error (&err); 253 | g_free (debug_info); 254 | g_main_loop_quit (loop); 255 | break; 256 | case GST_MESSAGE_EOS: 257 | g_print ("End-Of-Stream reached.\n"); 258 | g_main_loop_quit (loop); 259 | break; 260 | case GST_MESSAGE_STATE_CHANGED: 261 | /* We are only interested in state-changed messages from the pipeline */ 262 | if (GST_MESSAGE_SRC (msg) == GST_OBJECT (pipeline)) { 263 | GstState old_state, new_state, pending_state; 264 | gst_message_parse_state_changed (msg, &old_state, &new_state, &pending_state); 265 | g_print ("Pipeline state changed from %s to %s:\n", 266 | gst_element_state_get_name (old_state), gst_element_state_get_name (new_state)); 267 | } 268 | break; 269 | case GST_MESSAGE_ELEMENT:{ 270 | const GstStructure *s = gst_message_get_structure (msg); 271 | 272 | if (gst_structure_has_name (s, "GstBinForwarded")) { 273 | GstMessage *forward_msg = NULL; 274 | 275 | gst_structure_get (s, "message", GST_TYPE_MESSAGE, &forward_msg, NULL); 276 | if (GST_MESSAGE_TYPE (forward_msg) == GST_MESSAGE_EOS) { 277 | g_print ("EOS from element %s\n", 278 | GST_OBJECT_NAME (GST_MESSAGE_SRC (forward_msg))); 279 | gst_element_set_state (filesink, GST_STATE_NULL); 280 | gst_element_set_state (mp4mux, GST_STATE_NULL); 281 | gst_element_set_state (filesink, GST_STATE_PLAYING); 282 | gst_element_set_state (mp4mux, GST_STATE_PLAYING); 283 | } 284 | gst_message_unref (forward_msg); 285 | } 286 | } 287 | break; 288 | default: 289 | /* We should not reach here */ 290 | g_printerr ("Unexpected message received: %s\n", GST_MESSAGE_TYPE_NAME(msg)); 291 | break; 292 | } 293 | gst_message_unref (msg); 294 | } 295 | return TRUE; 296 | } 297 | 298 | bool create_pipeline() 299 | { 300 | g_print("create_pipeline\n"); 301 | 302 | std::string launch_string = 303 | "videotestsrc is-live=true" 304 | //"filesrc location=in.mp4" 305 | //" ! qtdemux ! h264parse" 306 | //" ! tee name=t ! queue ! fakesink name=fakesink sync=true t." 307 | //" ! queue max-size-bytes=0 max-size-time=0 max-size-buffers=0" 308 | //" ! taginject name=taginject" 309 | " ! x264enc" 310 | " ! mp4mux name=mp4mux" 311 | " ! filesink name=filesink location=out.mp4"; 312 | g_print("launch_string: %s\n", launch_string.c_str()); 313 | //g_object_set(src, "pattern", 2, NULL); 314 | 315 | pipeline = gst_parse_launch (launch_string.c_str(), NULL); 316 | 317 | mp4mux = gst_bin_get_by_name(GST_BIN(pipeline), "mp4mux"); 318 | filesink = gst_bin_get_by_name(GST_BIN(pipeline), "filesink"); 319 | 320 | appsrc = gst_element_factory_make("appsrc", NULL); 321 | GstCaps *caps = gst_caps_from_string("gpmf/x-raw"); 322 | 323 | /* Configure appsrc */ 324 | g_object_set (appsrc, "caps", caps, NULL); 325 | g_object_set(G_OBJECT(appsrc), 326 | "stream-type", 0, // GST_APP_STREAM_TYPE_STREAM 327 | "format", GST_FORMAT_TIME, 328 | "is-live", TRUE, 329 | //"do-timestamp", TRUE, 330 | NULL); 331 | 332 | gst_bin_add_many(GST_BIN(pipeline), appsrc, NULL); 333 | { 334 | GstPad *srcpad = gst_element_get_static_pad(appsrc, "src"); 335 | GstPad *sinkpad = gst_element_get_request_pad(mp4mux, "gpmf_0"); 336 | gst_pad_link (srcpad, sinkpad); 337 | } 338 | 339 | return true; 340 | } 341 | 342 | int main(int argc, char *argv[]) 343 | { 344 | gpmfhandle = GPMFWriteServiceInit(); 345 | if (gpmfhandle == 0) 346 | { 347 | g_print("Couldn't create gpmfhandle\n"); 348 | exit(1); 349 | } 350 | 351 | char bufACCL[4096]; 352 | char bufGYRO[8192]; 353 | char bufGPS[4096]; 354 | int16_t s; 355 | float f; 356 | char txt[80]; 357 | uint32_t err; 358 | 359 | handleACCL = GPMFWriteStreamOpen(gpmfhandle, GPMF_CHANNEL_TIMED, GPMF_DEVICE_ID_CAMERA, "Camera", bufACCL, sizeof(bufACCL)); 360 | if (handleACCL == 0) 361 | { 362 | g_print("Couldn't create handleACCL\n"); 363 | exit(1); 364 | } 365 | handleGYRO = GPMFWriteStreamOpen(gpmfhandle, GPMF_CHANNEL_TIMED, GPMF_DEVICE_ID_CAMERA, "Camera", bufGYRO, sizeof(bufGYRO)); 366 | if (handleGYRO == 0) 367 | { 368 | g_print("Couldn't create handleGYRO\n"); 369 | exit(1); 370 | } 371 | handleGPS = GPMFWriteStreamOpen(gpmfhandle, GPMF_CHANNEL_TIMED, GPMF_DEVICE_ID_CAMERA, "Camera", bufGPS, sizeof(bufGPS)); 372 | if (handleGPS == 0) 373 | { 374 | g_print("Couldn't create handleGPS\n"); 375 | exit(1); 376 | } 377 | handleISOG = GPMFWriteStreamOpen(gpmfhandle, GPMF_CHANNEL_TIMED, GPMF_DEVICE_ID_CAMERA, "Camera", NULL, 0); 378 | if (handleISOG == 0) 379 | { 380 | g_print("Couldn't create handleISOG\n"); 381 | exit(1); 382 | } 383 | //handleSHUT = GPMFWriteStreamOpen(gpmfhandle, GPMF_CHANNEL_TIMED, GPMF_DEVICE_ID_CAMERA, "Camera", NULL, 0); 384 | //if (handleSHUT == 0) 385 | //{ 386 | // g_print("Couldn't create handleSHUT\n"); 387 | // exit(1); 388 | //} 389 | 390 | //Initialize sensor stream with any sticky data 391 | //sprintf_s(txt, 80, "Accelerometer (up/down, right/left, forward/back)"); 392 | //err = GPMFWriteStreamStore(handleACCL, GPMF_KEY_STREAM_NAME, 'c', strlen(txt), 1, &txt, GPMF_FLAGS_STICKY); 393 | //if (err) printf("err = %d\n", err); 394 | char siun[5] = "m/sA"; 395 | siun[3] = 0xb2; // squared 396 | err = GPMFWriteStreamStore(handleACCL, STR2FOURCC("SIUN"), 'c', 4*sizeof(char), 1, &siun, GPMF_FLAGS_STICKY); 397 | if (err) printf("err = %d\n", err); 398 | s = 418; 399 | err = GPMFWriteStreamStore(handleACCL, GPMF_KEY_SCALE, 's', sizeof(s), 1, &s, GPMF_FLAGS_STICKY); 400 | if (err) printf("err = %d\n", err); 401 | f = 31.062; 402 | err = GPMFWriteStreamStore(handleACCL, STR2FOURCC("TMPC"), 'f', sizeof(f), 1, &f, GPMF_FLAGS_STICKY); 403 | if (err) printf("err = %d\n", err); 404 | 405 | //sprintf_s(txt, 80, "Gyroscope (z,x,y)"); 406 | //GPMFWriteStreamStore(handleGYRO, GPMF_KEY_STREAM_NAME, GPMF_TYPE_STRING_ASCII, strlen(txt), 1, &txt, GPMF_FLAGS_STICKY); 407 | sprintf_s(txt, 80, "rad/s"); 408 | GPMFWriteStreamStore(handleGYRO, STR2FOURCC("SIUN"), 'c', strlen(txt), 1, &txt, GPMF_FLAGS_STICKY); 409 | s = 3755; 410 | GPMFWriteStreamStore(handleGYRO, GPMF_KEY_SCALE, 's', sizeof(s), 1, &s, GPMF_FLAGS_STICKY); 411 | f = 31.062; 412 | GPMFWriteStreamStore(handleGYRO, STR2FOURCC("TMPC"), 'f', sizeof(f), 1, &f, GPMF_FLAGS_STICKY); 413 | 414 | uint32_t L = 0; 415 | GPMFWriteStreamStore(handleGPS, STR2FOURCC("GPSF"), 'L', sizeof(L), 1, &L, GPMF_FLAGS_STICKY); 416 | char utcdata[17] = "180120221500.515"; 417 | GPMFWriteStreamStore(handleGPS, STR2FOURCC("GPSU"), 'c', 16*sizeof(char), 1, &utcdata, GPMF_FLAGS_STICKY); 418 | //sprintf_s(txt, 80, "GPS (Lat., Long., Alt., 2D speed, 3D speed)"); 419 | //err = GPMFWriteStreamStore(handleGPS, GPMF_KEY_STREAM_NAME, 'c', strlen(txt), 1, &txt, GPMF_FLAGS_STICKY); 420 | //if (err) printf("err = %d\n", err); 421 | uint16_t S = 240; 422 | err = GPMFWriteStreamStore(handleGPS, STR2FOURCC("GPSP"), 'S', sizeof(S), 1, &S, GPMF_FLAGS_STICKY); 423 | if (err) printf("err = %d\n", err); 424 | sprintf_s(txt, 80, "degdegmAAm/sm/s"); 425 | txt[7] = '\0'; 426 | txt[8] = '\0'; 427 | err = GPMFWriteStreamStore(handleGPS, STR2FOURCC("UNIT"), 'c', 3*sizeof(char), 5, &txt, GPMF_FLAGS_STICKY); 428 | if (err) printf("err = %d\n", err); 429 | int32_t ss[5] = {10000000, 10000000, 1000, 1000, 100}; 430 | err = GPMFWriteStreamStore(handleGPS, GPMF_KEY_SCALE, 'l', sizeof(int32_t), 5, ss, GPMF_FLAGS_STICKY); 431 | if (err) printf("err = %d\n", err); 432 | 433 | sprintf_s(txt, 80, "Sensor gain (ISO x100)"); 434 | GPMFWriteStreamStore(handleISOG, GPMF_KEY_STREAM_NAME, GPMF_TYPE_STRING_ASCII, strlen(txt), 1, &txt, GPMF_FLAGS_STICKY); 435 | S = 500; 436 | err = GPMFWriteStreamStore(handleISOG, STR2FOURCC("NANA"), 'S', sizeof(S), 1, &S, GPMF_FLAGS_STICKY); // Non GoPro field to check if it gets recognized in any clients 437 | 438 | //sprintf_s(txt, 80, "Exposure time (shutter speed)"); 439 | //GPMFWriteStreamStore(handleSHUT, GPMF_KEY_STREAM_NAME, GPMF_TYPE_STRING_ASCII, strlen(txt), 1, &txt, GPMF_FLAGS_STICKY); 440 | //sprintf_s(txt, 80, "s"); 441 | //GPMFWriteStreamStore(handleSHUT, STR2FOURCC("SIUN"), 'c', strlen(txt), 1, &txt, GPMF_FLAGS_STICKY); 442 | 443 | //Flush any stale data before starting video capture. 444 | err = GPMFWriteGetPayload(gpmfhandle, GPMF_CHANNEL_TIMED, (uint32_t *)buffer, sizeof(buffer), &payload, &payload_size); 445 | if (err) printf("err = %d\n", err); 446 | 447 | 448 | 449 | 450 | loop = g_main_loop_new (NULL, TRUE); 451 | 452 | 453 | gst_init(&argc, &argv); 454 | 455 | pipeline = gst_pipeline_new("test-pipeline"); 456 | 457 | create_pipeline(); 458 | 459 | GstBus *bus = gst_pipeline_get_bus (GST_PIPELINE(pipeline)); 460 | gst_bus_add_watch (bus, (GstBusFunc) bus_message, NULL); 461 | gst_object_unref(bus); 462 | 463 | 464 | g_signal_connect (appsrc, "need-data", G_CALLBACK (start_feed), NULL); 465 | g_signal_connect (appsrc, "enough-data", G_CALLBACK (stop_feed), NULL); 466 | 467 | signal(SIGINT, shutdown); 468 | 469 | recording_beginning = msNow(); 470 | 471 | if (gst_element_set_state (pipeline, GST_STATE_PLAYING) == GST_STATE_CHANGE_FAILURE) 472 | { 473 | g_print("Couldn't play stream\n"); 474 | exit(1); 475 | } 476 | 477 | g_print("running...\n"); 478 | g_main_loop_run (loop); 479 | g_print("stopped\n"); 480 | 481 | //std::thread t0([&](){ 482 | // sleep(5); 483 | // push_data(NULL); 484 | // sleep(5); 485 | // push_data(NULL); 486 | // while(!terminate) 487 | // { 488 | // push_data(NULL); 489 | // usleep(100000); 490 | // } 491 | // }); 492 | 493 | /* create a server instance */ 494 | /* start serving */ 495 | gst_object_unref (bus); 496 | g_main_loop_unref (loop); 497 | gst_element_set_state (pipeline, GST_STATE_NULL); 498 | gst_object_unref (pipeline); 499 | 500 | return 0; 501 | } 502 | --------------------------------------------------------------------------------