Created
June 22, 2012 02:16
-
-
Save vi/2969810 to your computer and use it in GitHub Desktop.
udp2mkv and mkv2udp
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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; | |
} | |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/* | |
* 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; | |
} | |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
See tools from https://github.com/vi/HsMkv for less hacky implementation.