Internal change
PiperOrigin-RevId: 164326532
Change-Id: If3fd2a4036c54817edc5bd9c8bca0af49a31b381
diff --git a/example/muxing/Makefile b/example/muxing/Makefile
new file mode 100644
index 0000000..d3d7528
--- /dev/null
+++ b/example/muxing/Makefile
@@ -0,0 +1,32 @@
+CC=g++
+CFLAGS += -Werror -Wextra -Wall -g
+LDLIBS := $(shell pkg-config --libs mp4v2) $(LDLIBS)
+
+# Debug target to print variables.
+print-%:
+ @echo '$*=$($*)'
+
+all: camm_muxing.o
+ $(CC) camm_muxing.o $(LDLIBS) $(CFLAGS) -o camm_muxing
+
+clean:
+ rm camm_muxing camm_muxing.o
+
+# You may need to add something similar to the following package config
+# to your system's package config location. This may be located at, e.g.
+# "/usr/local/lib/pkgconfig/mp4v2.pc" for mp4v2. (Try "man pkg-config".)
+#
+#prefix=/usr/local
+#exec_prefix=${prefix}
+#libdir=/usr/local/lib
+#includedir=/usr/local/include
+#
+#Name: mp4v2
+#Description: MP4v2 library
+#Version: 1.0.0
+#Requires:
+#Requires.private:
+#Conflicts:
+#Libs: -L${libdir} -lmp4v2
+#Libs.private:
+#Cflags: -I${includedir}
diff --git a/example/muxing/camm_muxing.cc b/example/muxing/camm_muxing.cc
new file mode 100644
index 0000000..ba75725
--- /dev/null
+++ b/example/muxing/camm_muxing.cc
@@ -0,0 +1,317 @@
+/*
+ * This code sample muxes an artificially generated video stream with a
+ * data stream for camera motion metadata (CAMM) into a single mp4 file.
+ * The sample can be built with the "make" command.
+ * After building the sample, it can be run as follows:
+ *
+ * > ./camm_muxing output_file.mp4
+ *
+ * Note that this must be run in the sample directory as the "sample.jpg" file.
+ *
+ * The video stream is a single JPEG file for the entire video duration. It
+ * is for illustration purposes only. The calling code is expected to
+ * already have the encoded bytes of video frames to mux into the mp4 file.
+ */
+
+#include <endian.h>
+#include <math.h>
+#include <mp4v2/mp4v2.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/stat.h>
+#include <cstring>
+#include <fstream>
+#include <iostream>
+#include <vector>
+
+#define FRAME_WIDTH 2880
+#define FRAME_HEIGHT 1800
+#define VIDEO_TIME_SECONDS 10
+#define VIDEO_TIME_SCALE 90000
+#define NUM_CAMM_SAMPLES 200
+#define BYTE_BUFFER_SIZE 1000
+
+namespace {
+// Number of bytes for the packet type in a CAMM data packet.
+const int kCammPacketBytes = 4;
+// These sizes are fixed when building with IEEE floating point format.
+const int kMetadataTypeSizes[] = {
+ // 3 float angle_axis values.
+ 3 * sizeof(float),
+ // 2 int64 values for pixel_exposure_time and rolling_shutter_skew_time.
+ 2 * sizeof(uint64_t),
+ // 3 float gyro values.
+ 3 * sizeof(float),
+ // 3 float acceleration values.
+ 3 * sizeof(float),
+ // 3 float position values.
+ 3 * sizeof(float),
+ // 3 floats for latitude, longitude, and altitude.
+ 3 * sizeof(float),
+ // 3 doubles for time_gps_epoch, latitude, and longitude
+ // 1 int32 for gps_fix_type
+ // 7 floats for altitude, horizontal_accuracy, vertical_accuracy,
+ // vertical_east, vertical_north, vertical_up, and speed_accuracy.
+ 3 * sizeof(double) + sizeof(uint32_t) + 7 * sizeof(float),
+ // 3 floats for magnetic field.
+ 3 * sizeof(float)};
+
+static uint64_t double_to_bytes(double d) {
+ uint64_t result;
+ memcpy(&result, &d, 8);
+ return htole64(result);
+}
+
+static uint32_t float_to_bytes(float f) {
+ uint32_t result;
+ memcpy(&result, &f, 4);
+ return htole32(result);
+}
+
+// Writes sample camm data in a byte stream format for a single packet.
+// Returns the number of bytes written.
+// In real code, you are not required to write all packet types like in this
+// example. The pts of each packet also does not need to be equally spaced.
+static int WriteSampleCammBytes(void *bytes, int packet_number) {
+ uint32_t angle_x, angle_y, angle_z, gyro_x, gyro_y, gyro_z, acceleration_x,
+ acceleration_y, acceleration_z, position_x, position_y, position_z,
+ latitude32, longitude32, gps_fix_type, horizontal_accuracy_meters,
+ vertical_accuracy_meters, vertical_east_velocity_mps,
+ vertical_north_velocity_mps, speed_accuracy_mps, altitude,
+ magnetic_field_x, magnetic_field_y, magnetic_field_z,
+ pixel_exposure_nanoseconds, rolling_shutter_skew_time,
+ vertical_up_velocity_mps;
+ uint64_t latitude64, longitude64, time_gps_epoch;
+ memset(bytes, 0, BYTE_BUFFER_SIZE);
+ uint16_t *camm_data = reinterpret_cast<uint16_t *>(bytes);
+ uint16_t packet_type = packet_number % 8;
+ // The packet type comes after the first 2 reserved bytes.
+ camm_data[1] = htole16(packet_type);
+ camm_data += 2;
+ switch (packet_type) {
+ case 0:
+ angle_x = float_to_bytes(M_PI / 2);
+ angle_y = float_to_bytes(-M_PI / 2);
+ angle_z =
+ float_to_bytes(fmod(packet_number * M_PI / 20, 2 * M_PI) - M_PI);
+ memcpy(camm_data, &angle_x, 4);
+ memcpy(camm_data + 2, &angle_y, 4);
+ memcpy(camm_data + 4, &angle_z, 4);
+ break;
+ case 1:
+ pixel_exposure_nanoseconds = double_to_bytes(500);
+ rolling_shutter_skew_time = double_to_bytes(300);
+ memcpy(camm_data, &pixel_exposure_nanoseconds, 8);
+ memcpy(camm_data + 4, &rolling_shutter_skew_time, 8);
+ break;
+ case 2:
+ gyro_x = float_to_bytes(M_PI / 20);
+ gyro_y = float_to_bytes(2 * M_PI / 20);
+ gyro_z = float_to_bytes(3 * M_PI / 20);
+ memcpy(camm_data, &gyro_x, 4);
+ memcpy(camm_data + 2, &gyro_y, 4);
+ memcpy(camm_data + 4, &gyro_z, 4);
+ break;
+ case 3:
+ acceleration_x = float_to_bytes(0.1);
+ acceleration_y = float_to_bytes(0.2);
+ acceleration_z = float_to_bytes(0.3);
+ memcpy(camm_data, &acceleration_x, 4);
+ memcpy(camm_data + 2, &acceleration_y, 4);
+ memcpy(camm_data + 4, &acceleration_z, 4);
+ break;
+ case 4:
+ position_x = 0;
+ position_y = 0;
+ position_z = 0;
+ memcpy(camm_data, &position_x, 4);
+ memcpy(camm_data + 2, &position_y, 4);
+ memcpy(camm_data + 4, &position_z, 4);
+ break;
+ case 5:
+ latitude32 = float_to_bytes(37.454356 + .001 * packet_number);
+ longitude32 = float_to_bytes(-122.167477 + 0.001 * packet_number);
+ altitude = 0;
+ memcpy(camm_data, &latitude32, 4);
+ memcpy(camm_data + 2, &longitude32, 4);
+ memcpy(camm_data + 4, &altitude, 4);
+ break;
+ case 6:
+ time_gps_epoch = double_to_bytes(1500507374.825 + packet_number);
+ memcpy(camm_data, &time_gps_epoch, 8);
+ camm_data += 4;
+ gps_fix_type = 0;
+ memcpy(camm_data, &gps_fix_type, 4);
+ camm_data += 2;
+ latitude64 = double_to_bytes(37.454356 + .001 * packet_number);
+ memcpy(camm_data, &latitude64, 8);
+ camm_data += 4;
+ longitude64 = double_to_bytes(-122.167477 + .001 * packet_number);
+ memcpy(camm_data, &longitude64, 8);
+ camm_data += 4;
+ altitude = 0;
+ memcpy(camm_data, &altitude, 4);
+ camm_data += 2;
+ horizontal_accuracy_meters = float_to_bytes(7.5);
+ memcpy(camm_data, &horizontal_accuracy_meters, 4);
+ camm_data += 2;
+ vertical_accuracy_meters = float_to_bytes(10.5);
+ memcpy(camm_data, &vertical_accuracy_meters, 4);
+ camm_data += 2;
+ vertical_east_velocity_mps = float_to_bytes(1.1);
+ memcpy(camm_data, &vertical_east_velocity_mps, 4);
+ camm_data += 2;
+ vertical_north_velocity_mps = float_to_bytes(1.1);
+ memcpy(camm_data, &vertical_north_velocity_mps, 4);
+ camm_data += 2;
+ vertical_up_velocity_mps = 0;
+ memcpy(camm_data, &vertical_up_velocity_mps, 4);
+ camm_data += 2;
+ speed_accuracy_mps = float_to_bytes(2.5);
+ memcpy(camm_data, &speed_accuracy_mps, 4);
+ break;
+ case 7:
+ magnetic_field_x = float_to_bytes(0.01);
+ magnetic_field_y = float_to_bytes(0.01);
+ magnetic_field_z = float_to_bytes(0.01);
+ memcpy(camm_data, &magnetic_field_x, 4);
+ memcpy(camm_data + 2, &magnetic_field_y, 4);
+ memcpy(camm_data + 4, &magnetic_field_z, 4);
+ break;
+ default:
+ break;
+ }
+ return kCammPacketBytes + kMetadataTypeSizes[packet_type];
+}
+
+static void WriteCammPacket(MP4FileHandle handle, int camm_track_id, int pts,
+ int packet_num, char *buffer) {
+ int packet_size = WriteSampleCammBytes(buffer, packet_num);
+ if (!MP4WriteSample(handle, camm_track_id,
+ reinterpret_cast<uint8_t *>(buffer), packet_size,
+ /* duration */ 0,
+ /* rendering_offset */ pts,
+ /* is_sync_sample */ true)) {
+ printf("Failed to write camm track sample\n");
+ exit(1);
+ }
+}
+
+static void WriteVideoFrame(MP4FileHandle handle, int video_track_id,
+ const std::vector<char> &jpg_bytes, int pts,
+ int duration) {
+ // This code sample doesn't illustrate how to encode video. An encoder
+ // for h264 or another format should be used to do that, and then call
+ // MP4WriteSample with the bytes returned from the encoder.
+ if (!MP4WriteSample(handle, video_track_id,
+ reinterpret_cast<const uint8_t *>(jpg_bytes.data()),
+ /* num_bytes */ jpg_bytes.size(),
+ /* duration */ VIDEO_TIME_SCALE,
+ /* rendering_offset */ pts,
+ /* is_sync_sample */ true)) {
+ printf("Failed to write mp4 sample to video track\n");
+ exit(1);
+ }
+}
+
+static void WriteStreams(MP4FileHandle handle, int video_track_id,
+ const std::vector<char> &jpg_bytes,
+ int camm_track_id) {
+ char camm_buffer[BYTE_BUFFER_SIZE];
+ int video_pts = 0;
+ int camm_pts = 0;
+ int camm_packet_num = 0;
+ int max_pts = VIDEO_TIME_SECONDS * VIDEO_TIME_SCALE;
+ while (video_pts < max_pts && camm_pts < max_pts) {
+ if (video_pts <= camm_pts) {
+ WriteVideoFrame(handle, video_track_id, jpg_bytes, video_pts,
+ VIDEO_TIME_SCALE);
+ video_pts += VIDEO_TIME_SCALE;
+ } else {
+ WriteCammPacket(handle, camm_track_id, camm_pts, camm_packet_num,
+ camm_buffer);
+ camm_pts += max_pts / NUM_CAMM_SAMPLES;
+ camm_packet_num++;
+ }
+ }
+}
+
+static int AddVideoTrack(MP4FileHandle handle) {
+ printf("Adding video track\n");
+ int video_track_id = MP4AddVideoTrack(
+ handle, VIDEO_TIME_SCALE, VIDEO_TIME_SCALE * VIDEO_TIME_SECONDS,
+ FRAME_WIDTH, FRAME_HEIGHT, MP4_JPEG_VIDEO_TYPE);
+ if (video_track_id == MP4_INVALID_TRACK_ID) {
+ printf("Can't add video track\n");
+ exit(1);
+ }
+ return video_track_id;
+}
+
+static int AddCammTrack(MP4FileHandle handle) {
+ printf("Adding camm track\n");
+ int data_track_id = MP4AddTrack(handle, "camm", VIDEO_TIME_SCALE);
+ if (data_track_id == MP4_INVALID_TRACK_ID) {
+ printf("Can't add camm data track\n");
+ exit(1);
+ }
+ if (!MP4SetTrackName(handle, data_track_id, "camm")) {
+ printf("Couldn't set the track name of the CAMM track.\n");
+ exit(1);
+ }
+ return data_track_id;
+}
+
+// Reads the contents of the sample jpeg file into memory.
+static std::vector<char> ReadSampleFileBytes() {
+ struct stat buffer;
+ const char *filename = "sample.jpg";
+ if (stat(filename, &buffer) != 0) {
+ printf(
+ "Sample file 'sample.jpg' doesn't exist\n"
+ "Make sure it is in the directory and then run the program\n"
+ "again.\n");
+ exit(1);
+ }
+ std::ifstream ifs(filename, std::ios::binary | std::ios::ate);
+ std::ifstream::pos_type pos = ifs.tellg();
+ std::vector<char> result(pos);
+ ifs.seekg(0, std::ios::beg);
+ ifs.read(&result[0], pos);
+ return result;
+}
+
+static void VerifyProgramArgs(int argc, char **argv) {
+ if (argc != 2) {
+ printf(
+ "Usage: %s file.mp4\n"
+ "file.mp4 is the output location of the generated video\n",
+ argv[0]);
+ exit(1);
+ }
+}
+} // namespace
+
+int main(int argc, char **argv) {
+#ifndef __STDC_IEC_559__
+ fprintf(stderr,
+ "Please ensure your compiler uses IEEE 754 \n"
+ "floating point representation. If so, you may safely comment out \n"
+ "this guard.\n");
+ exit(1);
+#endif
+
+ VerifyProgramArgs(argc, argv);
+ std::vector<char> jpg_bytes = ReadSampleFileBytes();
+ MP4FileHandle handle = MP4Create(argv[1], /* flags */ 0);
+ int video_track_id = AddVideoTrack(handle);
+ int camm_track_id = AddCammTrack(handle);
+
+ WriteStreams(handle, video_track_id, jpg_bytes, camm_track_id);
+
+ printf("Dumping MP4 file information:\n");
+ MP4Dump(handle);
+ MP4Close(handle);
+ printf("Done!\n");
+ return 0;
+}
diff --git a/example/muxing/sample.jpg b/example/muxing/sample.jpg
new file mode 100644
index 0000000..c76d552
--- /dev/null
+++ b/example/muxing/sample.jpg
Binary files differ