camogm_ogm.c 7.73 KB
Newer Older
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15
/** @file camogm_ogm.c
 * @brief Provides writing to OGM files for @e camogm
 * @copyright Copyright (C) 2016 Elphel, Inc.
 *
 * @par <b>License</b>
 * This program is free software: you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation, either version 3 of the License, or
 * (at your option) any later version.
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * You should have received a copy of the GNU General Public License
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
Mikhail Karpenko's avatar
Mikhail Karpenko committed
16
 */
17

18 19 20 21 22
#include <stdio.h>
#include <stdlib.h>
#include <fcntl.h>
#include <sys/uio.h>
#include <errno.h>
23
#include <string.h>
24 25

#include "camogm_ogm.h"
26

27 28 29
/**
 * @brief Called when format is changed to OGM (only once) and recording is stopped
 */
Mikhail Karpenko's avatar
Mikhail Karpenko committed
30 31 32
int camogm_init_ogm(void)
{
	return 0;
33
}
34

Mikhail Karpenko's avatar
Mikhail Karpenko committed
35 36
void camogm_free_ogm(void)
{
37 38
}

39 40 41 42 43
/**
 * @brief Start OGM recording
 * @param[in]   state   a pointer to a structure containing current state
 * @return      0 if recording started successfully and negative error code otherwise
 */
44
int camogm_start_ogm(camogm_state *state)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
45 46 47 48
{
	char vendor[] = "ElphelOgm v 0.1";
	int pos;
	stream_header sh;
49
	char hdbuf[sizeof(sh) + 1];
Mikhail Karpenko's avatar
Mikhail Karpenko committed
50 51
	ogg_packet ogg_header;

52
	sprintf(state->path, "%s%010ld_%06ld.ogm", state->path_prefix, state->frame_params[state->port_num].timestamp_sec, state->frame_params[state->port_num].timestamp_usec);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
53 54 55 56 57 58 59 60 61 62 63 64 65 66
	if (!((state->vf = fopen(state->path, "w+"))) ) {
		D0(fprintf(debug_file, "Error opening %s for writing\n", state->path));
		return -CAMOGM_FRAME_FILE_ERR;
	}
	ogg_stream_init(&(state->os), state->serialno);
	state->packetno = 0;
	memset(&sh, 0, sizeof(stream_header));
	memcpy(sh.streamtype, "video", 5);
	memcpy(sh.subtype, "MJPG", 4);
	put_uint32(&sh.size, sizeof(sh));
	put_uint64(&sh.time_unit, state->time_unit);
	put_uint64(&sh.samples_per_unit, (ogg_int64_t)state->timescale);
	put_uint32(&sh.default_len, 1);
	put_uint32(&sh.buffersize, state->width * state->height);
67
	put_uint16(&sh.bits_per_sample, 0);
Mikhail Karpenko's avatar
Mikhail Karpenko committed
68 69 70 71
	put_uint32(&sh.sh.video.width, state->width);
	put_uint32(&sh.sh.video.height, state->height);
	memcpy(&hdbuf[1], &sh, sizeof(sh));
	hdbuf[0] = 1;
72
	// put it into Ogg stream
73
	ogg_header.packet = (unsigned char *)hdbuf;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
74 75 76 77 78 79
	ogg_header.bytes = sizeof(sh) + 1;
	ogg_header.b_o_s = 1;
	ogg_header.e_o_s = 0;
	ogg_header.packetno = state->packetno++;;
	ogg_header.granulepos = 0;
	ogg_stream_packetin(&(state->os), &ogg_header);
80

81
	// while(ogg_stream_pageout(&(state->os), &(state->og))) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
82
	while (ogg_stream_flush(&(state->os), &(state->og))) {
83
		int i;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
84
		if ((((i = fwrite(state->og.header, 1, state->og.header_len, state->vf))) != state->og.header_len) ||
85
				(state->og.body_len && (((i = fwrite(state->og.body, 1, state->og.body_len, state->vf))) != state->og.body_len))) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
86 87 88 89
			D2(fprintf(debug_file, "\n%d %ld %ld\n", i, state->og.header_len, state->og.body_len));
			return -CAMOGM_FRAME_FILE_ERR;
		}
	}
90

91 92
	// create comment
	// use fixed minimal one - hdbuf will be enough for that
Mikhail Karpenko's avatar
Mikhail Karpenko committed
93 94 95 96 97 98 99 100 101 102 103
	memset(hdbuf, 0, sizeof(hdbuf));
	hdbuf[0] = PACKET_TYPE_COMMENT;
	memcpy(&hdbuf[1], "vorbis", 6);
	pos = 7;
	put_uint32(&hdbuf[pos], strlen(vendor));
	pos += 4;
	strcpy(&hdbuf[pos], vendor);
	pos += strlen(vendor);
	put_uint32(&hdbuf[pos], 0);
	pos += 4;
	hdbuf[pos++] = 1;
104
	// put it into Ogg stream
105
	ogg_header.packet = (unsigned char *)hdbuf;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
106 107 108 109 110 111
	ogg_header.bytes = pos;
	ogg_header.b_o_s = 0;
	ogg_header.e_o_s = 0;
	ogg_header.packetno = state->packetno++;;
	ogg_header.granulepos = 0;
	ogg_stream_packetin(&(state->os), &ogg_header);
112 113
	// calculate initial absolute granulepos (from 1970), then increment with each frame. Later try calculating granulepos of each frame
	// from the absolute time (actual timestamp)
114
	state->granulepos = (ogg_int64_t)( (((double)state->frame_params[state->port_num].timestamp_usec) +
115 116 117 118 119
			(((double)1000000) * ((double)state->frame_params[state->port_num].timestamp_sec))) *
			((double)10) /
			((double)state->time_unit) *
			((double)state->timescale));
	// temporarily setting granulepos to 0 (suspect they do not process correctly 64 bits)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
120
	state->granulepos = 0;
121

122
	// Here - Ogg stream started, both header and comment  packets are sent out, next should be just data packets
Mikhail Karpenko's avatar
Mikhail Karpenko committed
123
	return 0;
124 125
}

126 127 128 129 130
/**
 * @brief Write a frame to file
 * @param[in]   state   a pointer to a structure containing current state
 * @return      0 if frame was saved successfully and negative error code otherwise
 */
131
int camogm_frame_ogm(camogm_state *state)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
132 133 134 135 136 137 138
{
	int indx;
	elph_ogg_packet elp_packet;

	elp_packet.bytes = 0;
	for (indx = 0; indx < state->chunk_index; indx++) elp_packet.bytes += state->packetchunks[indx].bytes;
	elp_packet.packet = state->packetchunks;
139 140
	//D(fprintf (debug_file,"elp_packet.bytes=0x%lx: elp_packet.packet=%p\n",elp_packet.bytes, elp_packet.packet));
	/*D(fprintf (debug_file,"0:0x%lx: %p\n" \
141 142 143 144 145 146 147 148 149 150 151 152 153
                             "1:0x%lx: %p\n" \
                             "2:0x%lx: %p\n" \
                             "3:0x%lx: %p\n" \
                             "4:0x%lx: %p\n" \
                             "5:0x%lx: %p\n" \
                             "6:0x%lx: %p\n", \
                              elp_packet.packet[0].bytes,  elp_packet.packet[0].chunk,
                              elp_packet.packet[1].bytes,  elp_packet.packet[1].chunk,
                              elp_packet.packet[2].bytes,  elp_packet.packet[2].chunk,
                              elp_packet.packet[3].bytes,  elp_packet.packet[3].chunk,
                              elp_packet.packet[4].bytes,  elp_packet.packet[4].chunk,
                              elp_packet.packet[5].bytes,  elp_packet.packet[5].chunk,
                              elp_packet.packet[6].bytes,  elp_packet.packet[6].chunk));
154
	 */
Mikhail Karpenko's avatar
Mikhail Karpenko committed
155 156 157 158
	elp_packet.b_o_s = 0;
	elp_packet.e_o_s = 0;
	elp_packet.packetno = state->packetno++;;
	elp_packet.granulepos = state->granulepos;
159
	/// @todo If that works, calculate granulepos from timestamp for each frame
Mikhail Karpenko's avatar
Mikhail Karpenko committed
160
	state->granulepos += (ogg_int64_t)state->timescale;
161

162
	//D3(fprintf (debug_file,"_121_"));
Mikhail Karpenko's avatar
Mikhail Karpenko committed
163
	ogg_stream_packetin_elph(&(state->os), &elp_packet);
164
	//D3(fprintf (debug_file,"_13_"));
Mikhail Karpenko's avatar
Mikhail Karpenko committed
165 166 167
	while (ogg_stream_pageout(&(state->os), &(state->og))) {
		int i, j;
		if ((((i = fwrite(state->og.header, 1, state->og.header_len, state->vf))) != state->og.header_len) ||
168
				(state->og.body_len && (((i = fwrite(state->og.body, 1, state->og.body_len, state->vf))) != state->og.body_len))) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
169 170 171 172 173 174
			j = errno;
			D0(fprintf(debug_file, "\n%d %ld %ld\n", i, state->og.header_len, state->og.body_len));
			return -CAMOGM_FRAME_FILE_ERR;
		}
	}
	return 0;
175 176
}

177 178 179 180
/**
 * @brief Finish OGM file operation
 * @param[in]   state   a pointer to a structure containing current state
 * @return      0 if file was saved successfully and negative error code otherwise
181
 * @note Zero packets are OK, use them to end file with "last" turned on
182
 */
183
int camogm_end_ogm(camogm_state *state)
Mikhail Karpenko's avatar
Mikhail Karpenko committed
184
{
185
	// put zero-packet it into stream
Mikhail Karpenko's avatar
Mikhail Karpenko committed
186 187 188 189 190 191 192 193
	ogg_packet ogg_header;

	ogg_header.packet = NULL;
	ogg_header.bytes = 0;
	ogg_header.b_o_s = 0;
	ogg_header.e_o_s = 1;
	ogg_header.packetno = state->packetno++;
	ogg_header.granulepos = ++(state->granulepos);
194
	ogg_stream_packetin(&(state->os), &ogg_header); // +++++++++++++++++++++++++++++++++++++++++++++++++++++
Mikhail Karpenko's avatar
Mikhail Karpenko committed
195
	while (ogg_stream_flush(&(state->os), &(state->og))) {
196
		int i;
Mikhail Karpenko's avatar
Mikhail Karpenko committed
197
		if ((((i = fwrite(state->og.header, 1, state->og.header_len, state->vf))) != state->og.header_len) ||
198
				(state->og.body_len && (((i = fwrite(state->og.body, 1, state->og.body_len, state->vf))) != state->og.body_len))) {
Mikhail Karpenko's avatar
Mikhail Karpenko committed
199 200 201 202 203
			D0(fprintf(debug_file, "\n%d %ld %ld\n", i, state->og.header_len, state->og.body_len));
			return -CAMOGM_FRAME_FILE_ERR;
		}
	}
	return 0;
204
}