Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
32 changes: 32 additions & 0 deletions example/muxing/Makefile
Original file line number Diff line number Diff line change
@@ -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}
317 changes: 317 additions & 0 deletions example/muxing/camm_muxing.cc
Original file line number Diff line number Diff line change
@@ -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;
}
Binary file added example/muxing/sample.jpg
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.