Skip to content

Instantly share code, notes, and snippets.

@vi
Created June 22, 2012 02:16
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save vi/2969810 to your computer and use it in GitHub Desktop.
Save vi/2969810 to your computer and use it in GitHub Desktop.
udp2mkv and mkv2udp
/*
* Read limited subset of matroska files (generated by udp2mkv) and stream them to network as UDP packets
*
* Limitations:
* 1. cluster size is 5 bytes: 08 NN NN NN NN
* 2. Each cluster have only two elements: Timecode and SimpleBlock
* 3. Timecode have size 8 bytes and it is microseconds (timecode scale = 1000)
* 4. SimpleBlock have size as 5 bytes: 08 NN NN NN NN
* 5. There is only one track and it is track number 1
* 6. No lacing
* i.e. frame data begins at fixed offset after the start of cluster
*
* For similar program, but without these limitations see HsMkv's ExampleUdpSend
* source: https://github.com/vi/HsMkv
* binary windows: http://vi-server.org/pub/HsMkv.exe
* binary linux: http://vi-server.org/pub/HsMkv
*
* License=MIT; Vitaly "_Vi" Shukela; 2012.
*
* i586-mingw32msvc-gcc mkv2udp.c -O2 -lws2_32 -o mkv2udp.exe
* gcc -O2 mkv2udp.c -o mkv2udp
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#ifndef WIN32
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <endian.h>
#else
#include <winsock2.h>
// Remove these things if they conflict with already defined ones
#if __BYTE_ORDER == __LITTLE_ENDIAN
// http://www.linux.org.ru/forum/development/4087771
uint16_t htobe16(uint16_t x) {
union { uint16_t u16; uint8_t v[2]; } ret;
ret.v[0] = (uint8_t)(x >> 8);
ret.v[1] = (uint8_t) x;
return ret.u16;
}
uint32_t htobe32(uint32_t x) {
union { uint32_t u32; uint8_t v[4]; } ret;
ret.v[0] = (uint8_t)(x >> 24);
ret.v[1] = (uint8_t)(x >> 16);
ret.v[2] = (uint8_t)(x >> 8);
ret.v[3] = (uint8_t) x;
return ret.u32;
}
uint64_t htobe64(uint64_t x) {
union { uint64_t u64; uint8_t v[8]; } ret;
ret.v[0] = (uint8_t)(x >> 56);
ret.v[1] = (uint8_t)(x >> 48);
ret.v[2] = (uint8_t)(x >> 40);
ret.v[3] = (uint8_t)(x >> 32);
ret.v[4] = (uint8_t)(x >> 24);
ret.v[5] = (uint8_t)(x >> 16);
ret.v[6] = (uint8_t)(x >> 8);
ret.v[7] = (uint8_t) x;
return ret.u64;
}
#else
#define htobe16(x) (x)
#define htobe32(x) (x)
#define htobe64(x) (x)
#endif
#endif
char buf[240*1024];
int udp_socket() {
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_socket == -1) {
perror("socket");
return -1;
}
/*
int rxsockbufsz = 64 * 1024;
if (setsockopt (udp_socket, SOL_SOCKET, SO_SNDBUF,
&rxsockbufsz, sizeof (rxsockbufsz))) {
perror("setsockopt SO_RCVBUF");
}*/
return udp_socket;
}
// length must be at least 16
// debt must be initialized to 0 initially
// @return 0 - no frame, call again; length - frame in buffer with this length; -1 - EOF
int resync_and_receive_one_frame(FILE* f, unsigned char* buffer, size_t length, size_t* debt, unsigned long long *timecode) {
int initial_recv = 16;
int r = fread(buffer + *debt, initial_recv - *debt, 1, f);
if (!r) return -1;
int i;
int found=0;
for(i=0; i<initial_recv - 3; ++i) {
if(
buffer[i+0]==0x1F &&
buffer[i+1]==0x43 &&
buffer[i+2]==0xB6 &&
buffer[i+3]==0x75) {
found=1;
break;
}
}
if(found) {
memmove(buffer, buffer+i, initial_recv-i);
// Now cluster is at the beginning of buffer
long int cluster_length;
memcpy(&cluster_length, buffer+5, 4);
cluster_length = htobe32(cluster_length);
size_t total_length = cluster_length + 9;
if(total_length > length) {
total_length = length;
}
if(total_length < 29) {
*debt = 0;
return 0;
}
fread(buffer+(initial_recv-i), total_length-(initial_recv-i), 1, f);
memcpy(timecode, buffer+11, 8);
*timecode = htobe64(*timecode);
memmove(buffer, buffer+29, total_length-29);
// Now content at the beginning of buffer
*debt = 0;
return total_length-29;
} else {
memmove(buffer, buffer+i, initial_recv-i);
*debt = initial_recv - i;
return 0;
}
}
int main(int argc, char* argv[]) {
if(argc<3) {
fprintf(stderr,
"mkv2udp v0.1 by Vitaly \"_Vi\" Shukela; License=MIT\n"
"\n"
"Usage: mkv2udp host port < file.mkv\n"
" Read mkv file generated by mkv2udp and\n"
" send frames as UDP packets to host:port\n"
" See also HsMkv's ExampleUdpSend: https://github.com/vi/HsMkv/\n");
return 2;
}
#ifdef WIN32
WSADATA wsaData;
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if ( iResult != NO_ERROR ) {
fprintf(stderr,"Error at WSAStartup()\n");
exit(1);
}
_setmode(_fileno(stdin), _O_BINARY);
#endif
struct sockaddr_in sa = {AF_INET, htons(atoi(argv[2])), {inet_addr(argv[1])}};
sa.sin_addr.s_addr = inet_addr(argv[1]);
int sock = udp_socket();
if (sock == -1) {
return 1;
}
unsigned long long timecode;
size_t tmp = 0;
int ret;
unsigned long long first_frame_timecode=0;
unsigned long long timebase=0;
for(;;) {
ret = resync_and_receive_one_frame(stdin, buf, sizeof buf, &tmp, &timecode);
if(ret==-1) break;
if(ret>0) {
if(timebase==0) {
struct timeval tv;
gettimeofday(&tv, NULL);
timebase = tv.tv_sec * 1000000LL + tv.tv_usec;
first_frame_timecode = timecode;
}
for(;;) {
struct timeval tv;
gettimeofday(&tv, NULL);
unsigned long long int now = tv.tv_sec * 1000000LL + tv.tv_usec;
long long int delay = (timecode - first_frame_timecode) - (now - timebase);
if(delay<0) delay=0;
if(delay>999999) {
usleep(900000);
continue;
} else {
usleep(delay);
break;
}
}
if(sendto(sock, buf, ret, 0, &sa, sizeof sa) == -1) {
perror("sendto");
return 3;
}
fputc('.', stdout); fflush(stdout);
}
}
fputc('\n', stdout); fflush(stdout);
return 0;
}
/*
* Reveive UDP packets (possible joining multicast group) and write them to matroska file (to stdout)
* License=MIT; Vitaly "_Vi" Shukela; 2012.
*
* i586-mingw32msvc-gcc udp2mkv.c -O2 -lws2_32 -o udp2mkv.exe
* gcc -O2 udp2mkv.c -o udp2mkv
*/
#include <stdio.h>
#include <sys/types.h>
#include <sys/time.h>
#include <stdlib.h>
#include <unistd.h>
#include <fcntl.h>
#include <string.h>
#ifndef WIN32
#include <arpa/inet.h>
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
#include <endian.h>
#else
#include <winsock2.h>
// Remove these things if they conflict with already defined ones
#if __BYTE_ORDER == __LITTLE_ENDIAN
// http://www.linux.org.ru/forum/development/4087771
uint16_t htobe16(uint16_t x) {
union { uint16_t u16; uint8_t v[2]; } ret;
ret.v[0] = (uint8_t)(x >> 8);
ret.v[1] = (uint8_t) x;
return ret.u16;
}
uint32_t htobe32(uint32_t x) {
union { uint32_t u32; uint8_t v[4]; } ret;
ret.v[0] = (uint8_t)(x >> 24);
ret.v[1] = (uint8_t)(x >> 16);
ret.v[2] = (uint8_t)(x >> 8);
ret.v[3] = (uint8_t) x;
return ret.u32;
}
uint64_t htobe64(uint64_t x) {
union { uint64_t u64; uint8_t v[8]; } ret;
ret.v[0] = (uint8_t)(x >> 56);
ret.v[1] = (uint8_t)(x >> 48);
ret.v[2] = (uint8_t)(x >> 40);
ret.v[3] = (uint8_t)(x >> 32);
ret.v[4] = (uint8_t)(x >> 24);
ret.v[5] = (uint8_t)(x >> 16);
ret.v[6] = (uint8_t)(x >> 8);
ret.v[7] = (uint8_t) x;
return ret.u64;
}
#else
#define htobe16(x) (x)
#define htobe32(x) (x)
#define htobe64(x) (x)
#endif
// Remove these things if they conflict with already defined ones
#define IP_ADD_MEMBERSHIP 12
struct ip_mreq {
struct in_addr imr_multiaddr; /* IP multicast address of group */
struct in_addr imr_interface; /* local IP address of interface */
};
#endif
char buf[240*1024];
int udp_listen(const char* host, int port) {
int udp_socket = socket(AF_INET, SOCK_DGRAM, 0);
if (udp_socket == -1) {
perror("socket");
return -1;
}
struct sockaddr_in sa = {AF_INET, htons(port), {inet_addr(host)}};
if(bind(udp_socket, (struct sockaddr*)&sa, sizeof sa)==-1) {
perror("bind");
close(udp_socket);
return -1;
}
int rxsockbufsz = 240 * 1024;
if (setsockopt (udp_socket, SOL_SOCKET, SO_RCVBUF,
&rxsockbufsz, sizeof (rxsockbufsz))) {
perror("setsockopt SO_RCVBUF");
}
if ((ntohl (sa.sin_addr.s_addr) >> 28) == 0xe) {
struct ip_mreq mcast = {{sa.sin_addr.s_addr}, {0}};
if (setsockopt (udp_socket, IPPROTO_IP, IP_ADD_MEMBERSHIP, &mcast, sizeof (mcast))) {
perror("setsockopt IP_ADD_MEMBERSHIP");
}
}
return udp_socket;
}
void write_matroska_header(FILE* f, unsigned char track_type, const char* codec_id) {
fwrite("\x1a\x45\xdf\xa3\xa3\x42\x86\x81\x01\x42\xf7\x81\x01"
"\x42\xf2\x81\x04\x42\xf3\x81\x08\x42\x82\x88matroska"
"\x42\x87\x81\x02\x42\x85\x81\x02" // EBML header
"\x18\x53\x80\x67\xff", // Start of infinite segment
0x2d, 1, f);
fwrite("\x15\x49\xA9\x66" // Info
"\x90" // info size
"\x57\x41\x87udp2mkv" // writing application
"\x2A\xD7\xB1\x82\x03\xE8" // timecode scale = 1000 nanoseconds
, 21, 1, stdout);
size_t codec_id_len = strlen(codec_id);
unsigned long tracks_size = codec_id_len + 22;
unsigned long track_size = codec_id_len + 16;
unsigned long codecID_size = codec_id_len;
tracks_size = htobe32(tracks_size );
track_size = htobe32(track_size );
codecID_size = htobe32(codecID_size);
fwrite("\x16\x54\xAE\x6B\x08",5,1,f); // Tracks header
fwrite(&tracks_size, 4, 1, f);
fwrite("\xAE\x08", 2, 1, f); // TrackEntry header
fwrite(&track_size, 4, 1, f);
fwrite("\xD7\x81\x01", 3, 1, f); // TrackNumber
fwrite("\x73\xC5\x81\x01", 4, 1, f); // TrackUID
fwrite("\x83\x81\x02", 3, 1, f); // Track type
fwrite("\x86\x08", 2, 1, f); // CodecID header
fwrite(&codecID_size, 4, 1, f);
fwrite(codec_id, codec_id_len, 1, f);
fflush(f);
}
// timecode is in microseconds
void write_matroska_frame(FILE* f, unsigned long long int timecode, const char* buffer, size_t length) {
fwrite("\x1F\x43\xB6\x75\x08", 5, 1, f); // Cluster with part of size
long int cluster_size = length + 20;
cluster_size = htobe32(cluster_size);
fwrite(&cluster_size, 4, 1, f);
fwrite("\xE7\x88", 2, 1, f); // Timecode
timecode = htobe64(timecode);
fwrite(&timecode, 8, 1, f);
fwrite("\xA3\x08", 2, 1, f); // Simpleblock with part of size
long int simpleblock_size = length + 4;
simpleblock_size = htobe32(simpleblock_size);
fwrite(&simpleblock_size, 4, 1, f);
fwrite("\x81\x00\x00\x00", 4, 1, f);
fwrite(buffer, length, 1, f);
fflush(f);
}
int main(int argc, char* argv[]) {
if(argc<3) {
fprintf(stderr,
"udp2mkv v0.1 by Vitaly \"_Vi\" Shukela; License=MIT\n"
"\n"
"Usage: udp2mkv host port > file.mkv\n"
" Receive UDP packets on host:port\n"
" and store them to matroska file\n"
" (CodecID=A_MPEG/L3)\n"
" The file can be re-streamed using mkv2udp or HsMkv's ExampleUdpSend\n");
return 2;
}
#ifdef WIN32
WSADATA wsaData;
int iResult = WSAStartup( MAKEWORD(2,2), &wsaData );
if ( iResult != NO_ERROR ) {
fprintf(stderr,"Error at WSAStartup()\n");
exit(1);
}
_setmode(_fileno(stdout), _O_BINARY);
#endif
int udp_socket = udp_listen(argv[1], atoi(argv[2]));
if (udp_socket == -1) {
return 1;
}
const char *codecId = "A_MPEG/L3";
unsigned char type=0x02;
if(getenv("CODECID")) codecId = getenv("CODECID");
if(getenv("TRACKTYPE")) type = atoi(getenv("TRACKTYPE"));
write_matroska_header(stdout, type, codecId);
for(;;) {
struct timeval tv;
int s = recv(udp_socket, buf, sizeof buf, 0);
if(s==-1) {
break;
}
gettimeofday(&tv, NULL);
unsigned long long int timecode = tv.tv_usec + tv.tv_sec*1000000LL;
write_matroska_frame(stdout, timecode, buf, s);
}
return 0;
}
@vi
Copy link
Author

vi commented Jun 22, 2012

See tools from https://github.com/vi/HsMkv for less hacky implementation.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment