diff options
Diffstat (limited to 'Src/nsv/nsvlib.cpp')
-rw-r--r-- | Src/nsv/nsvlib.cpp | 737 |
1 files changed, 737 insertions, 0 deletions
diff --git a/Src/nsv/nsvlib.cpp b/Src/nsv/nsvlib.cpp new file mode 100644 index 00000000..e26bef2f --- /dev/null +++ b/Src/nsv/nsvlib.cpp @@ -0,0 +1,737 @@ +/* +** nsvlib.cpp - NSV file/bitstream reading/writing code +** +** Copyright (C) 2001-2002 Nullsoft, Inc. +** +** Confidential Subject to NDA +*/ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <bfc/platform/strcmp.h> + +#include "nsvlib.h" + +#define NSV_HDR_DWORD (NSV_MAKETYPE('N','S','V','f')) + +#define NSV_SYNC_HEADERLEN_BITS 192 +#define NSV_SYNC_DWORD (NSV_MAKETYPE('N','S','V','s')) +#define NSV_NONSYNC_HEADERLEN_BITS 56 +#define NSV_NONSYNC_WORD 0xBEEF + +#define NSV_INVALID_SYNC_OFFSET 0x80000000 + +long glSyncFrameCount = 0l; +long glCounterNSVf = 0l; +long glNonSyncFrameCount = 0l; + +/* + NSV sync packet header + 32 bits: NSV_SYNC_DWORD + 32 bits: video format + 32 bits: audio format + 16 bits: width + 16 bits: height + 8 bits: framerate (see getfrate/setfrate) + + + 16 bits: audio/video sync offset + +or + + NSV nonsync packet header + 16 bits: NSV_NONSYNC_WORD + +then + +4 bits: # aux data channels present (max 15) +20 bits: video data + aux channels length +16 bits: audio data length + +-------------------------------- +sync: + 192 bit header, + 136 bits are invariant +nonsync: + 56 bit header + 16 bits are invariant + +*/ + + + + +static int is_type_char_valid(int c) +{ + c&=0xff; + return (c >= 'a' && c <= 'z') || + (c >= 'A' && c <= 'Z') || + (c >= '0' && c <= '9') || + c == ' ' || c == '-' || + c == '.' || c == '_'; +} + +static int is_type_valid(unsigned int t) +{ + return (t&0xff) != ' ' && + is_type_char_valid(t>>24) && + is_type_char_valid(t>>16) && + is_type_char_valid(t>>8) && + is_type_char_valid(t); +} + + +void nsv_type_to_string(unsigned int t, char *out) +{ + if (is_type_valid(t)) + { + out[0]=(t)&0xff; + out[1]=(t>>8)&0xff; + out[2]=(t>>16)&0xff; + out[3]=(t>>24)&0xff; + out[4]=0; + int x=3; + while (out[x]==' ' && x > 0) out[x--]=0; + } + else *out=0; +} + +unsigned int nsv_string_to_type(char *in) +{ + int n; + unsigned int ret=*in; + if (*in == ' ' || !is_type_char_valid(*in)) return 0; + in++; + for (n = 0; n < 3; n ++) + { + if (!is_type_char_valid(*in)) break; + ret|=(*in<<(8+8*n)); + in++; + } + if (*in) return 0; + return ret; +} + +// frate is specified +// XYYYYYZZ +// if !X, framerate is YYYYYZZ (1-127) +// otherwise: +// ZZ indexes base +// YYYYY is scale (0-32). +// if YYYYY < 16, then scale = 1/(YYYY+1) +// otherwise scale = YYYYY-15 + + +static double frate2double(unsigned char fr) +{ + static double fratetab[]= + { + 30.0, + 30.0*1000.0/1001.0, + 25.0, + 24.0*1000.0/1001.0, + }; + if (!(fr&0x80)) return (double)fr; + + double sc; + int d=(fr&0x7f)>>2; + if (d < 16) sc=1.0/(double)(d+1); + else sc=d-15; + + return fratetab[fr&3]*sc; +} + + + +static unsigned char double2frate(double fr) +{ + int best=0; + double best_v=1000000.0; + int x; + for (x = 0; x < 256; x ++) + { + double this_v=(fr-frate2double(x)); + + if (this_v<0) this_v=-this_v; + if (this_v < best_v) + { + best_v=this_v; + best=x; + } + } + return (unsigned char) best; +} + + + +nsv_Packeter::nsv_Packeter() +{ + vidfmt=audfmt=0; + width=height=0; + framerate_idx=0; + framerate=0.0; + syncoffset_cur=0; + video=NULL; + audio=NULL; + video_len=0; + audio_len=0; + aux_used=0; +} + +void nsv_Packeter::setVidFmt(unsigned int vfmt, unsigned int w, unsigned int h, double frt) +{ + vidfmt=vfmt; + width=w; + height=h; + framerate=frt; + framerate_idx=double2frate(frt); +} + +nsv_Packeter::~nsv_Packeter() +{ +} + +int nsv_Packeter::packet(nsv_OutBS &bs) +{ + int total_auxlen=0; + int x; + if (width >= (1<<16) || height >= (1<<16) || + !framerate_idx || framerate_idx > 255 || + !is_type_valid(audfmt) || + !is_type_valid(vidfmt) || + video_len > NSV_MAX_VIDEO_LEN || + audio_len > NSV_MAX_AUDIO_LEN || + aux_used > NSV_MAX_AUXSTREAMS || + aux_used < 0 + ) return -1; + + for (x = 0; x < aux_used; x ++) + { + if (aux_len[x] > NSV_MAX_AUX_LEN) return -1; + total_auxlen+=aux_len[x]+6; + } + + if (is_sync_frame) + { + bs.putbits(32,NSV_SYNC_DWORD); + bs.putbits(32,vidfmt); + bs.putbits(32,audfmt); + bs.putbits(16,width); + bs.putbits(16,height); + bs.putbits(8 ,framerate_idx); + bs.putbits(16,syncoffset_cur); + } + else + { + bs.putbits(16,NSV_NONSYNC_WORD); + } + + bs.putbits(4,aux_used); // no aux data channels for our streams yet + bs.putbits(20,video_len+total_auxlen); + bs.putbits(16,audio_len); + + for (x = 0; x < aux_used; x ++) + { + bs.putbits(16,aux_len[x]); // length of 0 for aux channels + bs.putbits(32,aux_types[x]); + if (aux_len[x]) bs.putdata(aux_len[x]*8,aux[x]); + } + + if (video_len) bs.putdata(video_len*8,video); + if (audio_len) bs.putdata(audio_len*8,audio); + + return 0; +} + + +void nsv_Unpacketer::reset(int full) +{ + synched=0; + is_sync_frame=0; + syncoffset_cur=0; + syncoffset=NSV_INVALID_SYNC_OFFSET; + + if (full) + { + m_auxbs=NULL; + m_audiobs=NULL; + m_videobs=NULL; + m_eof=0; + vidfmt=0; + audfmt=0; + valid=0; + width=0; + height=0; + framerate=0.0; + framerate_idx=0; + } +} + + +// returns 0 on success, >0 on needs (at least X bytes) more data, +// -1 on error (no header found in block) +int nsv_Unpacketer::unpacket(nsv_InBS &bs) +{ + int gotframe=0; + unsigned int num_aux=0; + unsigned int vl=0; + unsigned int al=0; + + while (bs.avail()>=NSV_NONSYNC_HEADERLEN_BITS) + { + if (valid && synched) + { + if (bs.avail() < NSV_NONSYNC_HEADERLEN_BITS) + return m_eof?-1:(NSV_NONSYNC_HEADERLEN_BITS- (int)(bs.avail())/8); + + unsigned int d=bs.getbits(16); + if (d == NSV_NONSYNC_WORD) + { + glNonSyncFrameCount++; + num_aux=bs.getbits(4); + vl=bs.getbits(20); + al=bs.getbits(16); + if (al >= NSV_MAX_AUDIO_LEN || + vl >= (NSV_MAX_VIDEO_LEN+num_aux*(NSV_MAX_AUX_LEN+6))) + { + bs.seek(-NSV_NONSYNC_HEADERLEN_BITS); + } + else + { + if ((unsigned int)bs.avail() < 8*(vl+al)+(m_eof?0:32)) + { + int l=(al+vl+32/8)- (int)(bs.avail()/8); + bs.seek(-NSV_NONSYNC_HEADERLEN_BITS); + return m_eof?-1:l; + } + + if ((unsigned int)bs.avail() >= 8*(vl+al)+32) + { + bs.seek(8*(vl+al)); + unsigned int a32=bs.getbits(32); + bs.seek(-32); + unsigned int a16=bs.getbits(16); + bs.seek(-16); + bs.seek(-8*(vl+al)); + if (a16 != NSV_NONSYNC_WORD && a32 != NSV_SYNC_DWORD) + { + bs.seek(-NSV_NONSYNC_HEADERLEN_BITS); + } + else gotframe=NSV_NONSYNC_HEADERLEN_BITS; + } + else gotframe=NSV_NONSYNC_HEADERLEN_BITS; + } + } + else bs.seek(-16); + } // inf.valid && inf.synched + + // gotframe is set if we successfully got a nonsync frame, otherwise + // let's see if we can't interpret this as a sync frame + + if (!gotframe) + { + if (bs.avail() < NSV_SYNC_HEADERLEN_BITS) + return (int)(m_eof?-1:(NSV_SYNC_HEADERLEN_BITS-(bs.avail())/8)); + unsigned int d=bs.getbits(32); + if (d != NSV_SYNC_DWORD) + { + bs.seek(8-32); // seek back 3 bytes + synched=0; + continue; + }else{ + // count the # of sync frames (for debugging) + glSyncFrameCount++; + } + unsigned int vfmt=bs.getbits(32); + unsigned int afmt=bs.getbits(32); + unsigned int w=bs.getbits(16); + unsigned int h=bs.getbits(16); + unsigned char frt=bs.getbits(8); + unsigned int so=bs.getbits(16); + + num_aux=bs.getbits(4); + vl=bs.getbits(20); + al=bs.getbits(16); + + if (al >= NSV_MAX_AUDIO_LEN || + vl >= (NSV_MAX_VIDEO_LEN+num_aux*(NSV_MAX_AUX_LEN+6)) || + !frt || !is_type_valid(vfmt) || !is_type_valid(afmt) || + (valid && + (width != w || height != h || + vidfmt != vfmt || audfmt != afmt || framerate_idx != frt))) + { // frame is definately not valid + bs.seek(8-NSV_SYNC_HEADERLEN_BITS); // seek back what we just read + synched=0; + continue; + } + + if ((unsigned int)bs.avail() < (al+vl)*8+((m_eof||(valid&&synched))?0:32)) + { + int l=(al+vl)*8+NSV_SYNC_HEADERLEN_BITS- (int)(bs.avail()); + bs.seek(-NSV_SYNC_HEADERLEN_BITS); + return m_eof?-1:(l/8); + } + + if (valid && synched) + { + gotframe=NSV_SYNC_HEADERLEN_BITS; + } + else // we need to do more robust sync + { + int sk=(al+vl)*8; + bs.seek(sk); + unsigned int a16=bs.getbits(16); + bs.seek(-16); + unsigned int a32=bs.getbits(32); + bs.seek(-32); + if (a16 == NSV_NONSYNC_WORD) + { + sk+=16+4+20+16; + bs.seek(16); + unsigned int _num_aux=bs.getbits(4); + unsigned int _vl=bs.getbits(20); + unsigned int _al=bs.getbits(16); + if ((unsigned int)bs.avail() < (_vl+_al)*8 + 32) + { + int l=(_al+_vl+32)- (int)(bs.avail()/8); + bs.seek(-NSV_SYNC_HEADERLEN_BITS-sk); + return m_eof?-1:l; + } + bs.seek((_vl+_al)*8); + sk+=(_vl+_al)*8; + unsigned int a16=bs.getbits(16); + bs.seek(-16); + unsigned int a32=bs.getbits(32); + bs.seek(-32); + bs.seek(-sk); + if (a16 == NSV_NONSYNC_WORD || a32 == NSV_SYNC_DWORD) + gotframe=NSV_SYNC_HEADERLEN_BITS; + } + else if (a32 == NSV_SYNC_DWORD) + { + glSyncFrameCount++; + + sk+=32+32+32+16+16+8; + + bs.seek(32); + unsigned int _vfmt=bs.getbits(32); + unsigned int _afmt=bs.getbits(32); + unsigned int _w=bs.getbits(16); + unsigned int _h=bs.getbits(16); + unsigned char _frt=bs.getbits(8); + bs.seek(-sk); + + if (_vfmt==vfmt && _afmt==afmt && _w==w && _h==h && _frt==frt) // matches + { + gotframe=NSV_SYNC_HEADERLEN_BITS; + } + } + } + if (!gotframe) + { + synched=0; + bs.seek(8-NSV_SYNC_HEADERLEN_BITS); + } + else + { + if (so & 0x8000) so|=0xFFFF0000; + syncoffset_cur=so; + if (!valid || syncoffset == NSV_INVALID_SYNC_OFFSET) syncoffset=so; + if (!valid) framerate=frate2double(frt); + framerate_idx=frt; + width=w; + height=h; + audfmt=afmt; + vidfmt=vfmt; + valid=1; + synched=1; + } + } + + if (gotframe) + { + is_sync_frame = (gotframe == NSV_SYNC_HEADERLEN_BITS); + // read aux channels + int rd=gotframe; + unsigned int x; + for (x = 0; x < num_aux; x ++) + { + unsigned int l=bs.getbits(16); + unsigned int fmt=bs.getbits(32); + vl -= 4+2; + rd += 16+32; + + if (l > NSV_MAX_AUX_LEN) break; + + if (m_auxbs) + { + m_auxbs->addint(l); + m_auxbs->addint(fmt); + m_auxbs->add(bs.getcurbyteptr(),l); + } + bs.seek(l*8); // toss aux + + vl-=l; + rd+=l*8; + + if (vl<0) break; // invalid frame (aux channels add up to more than video) + } + if (x < num_aux) // oh shit, invalid frame + { + synched=0; + bs.seek(8-rd); + gotframe=0; + continue; + } + + if (m_videobs) + { + m_videobs->addint(vl); + m_videobs->add(bs.getcurbyteptr(),vl); + } + bs.seek(vl*8); + + if (m_audiobs) + { + m_audiobs->addint(al); + m_audiobs->add(bs.getcurbyteptr(),al); + } + bs.seek(al*8); + + return 0; + } + } // while + return m_eof?-1:(NSV_NONSYNC_HEADERLEN_BITS- (int)(bs.avail())/8); +} + + + + + + + + + +/* NSV file header +4: NSV_HDR_DWORD +4: length of header in bytes + -- may not be 0 or 0xFFFFFFFF. :) +4: length of file, in bytes (including header - if this is 0 we are invalid) + -- can be 0xFFFFFFFF which means unknown length +4: length of file, in milliseconds (max file length, 24 days or so) + -- can be 0xFFFFFFFF which means unknown length +4: metadata length +4: number of TOC entries allocated +4: number of TOC entries used +mdlen: metadata +TOC_alloc*4:offset in file at time t. +*/ + +void nsv_writeheader(nsv_OutBS &bs, nsv_fileHeader *hdr, unsigned int padto) +{ + if (hdr->toc_alloc < hdr->toc_size) + hdr->toc_alloc=hdr->toc_size; + + if (hdr->toc_ex && hdr->toc_alloc <= hdr->toc_size*2) + hdr->toc_alloc=hdr->toc_size*2+1; + + hdr->header_size = 4+4+4+4+4+hdr->metadata_len+4+4+4*hdr->toc_alloc; + + bs.putbits(32,NSV_HDR_DWORD); + bs.putbits(32,hdr->header_size>padto?hdr->header_size:padto); + if (hdr->file_lenbytes == 0xFFFFFFFF) bs.putbits(32,hdr->file_lenbytes); + else bs.putbits(32,hdr->file_lenbytes+(hdr->header_size>padto?hdr->header_size:padto)); + bs.putbits(32,hdr->file_lenms); + bs.putbits(32,hdr->metadata_len); + bs.putbits(32,hdr->toc_alloc); + bs.putbits(32,hdr->toc_size); + bs.putdata(hdr->metadata_len*8,hdr->metadata); + + unsigned int numtoc=hdr->toc_alloc; + unsigned int numtocused=hdr->toc_size; + unsigned int *toc=hdr->toc; + unsigned int *toc_ex=hdr->toc_ex; + unsigned int numtocused2=(toc_ex && hdr->toc_alloc > hdr->toc_size*2) ? (hdr->toc_size + 1): 0; + + while (numtoc--) + { + if (!numtocused) + { + if (numtocused2) + { + if (--numtocused2 == hdr->toc_size) // signal extended TOC :) + bs.putbits(32,NSV_MAKETYPE('T','O','C','2')); + else + bs.putbits(32,*toc_ex++); + } + else // extra (unused by this implementation but could be used someday so we fill it with 0xFF) space + bs.putbits(32,~0); + } + else if (toc) + { + bs.putbits(32,*toc++); + numtocused--; + } + else bs.putbits(32,0); + } + + unsigned int x; + for (x = hdr->header_size; x < padto; x ++) bs.putbits(8,0); +} + + +int nsv_readheader(nsv_InBS &bs, nsv_fileHeader *hdr) +{ + int s=0; + hdr->metadata=(void*)NULL; + hdr->toc=(unsigned int *)NULL; + hdr->toc_ex=(unsigned int *)NULL; + hdr->header_size=0; + hdr->file_lenbytes=~0; + hdr->file_lenms=~0; + hdr->toc_alloc=0; + hdr->toc_size=0; + hdr->metadata_len=0; + + if (bs.avail()<64) { + return 8- (int)(bs.avail()/8); + } + s+=32; + if (bs.getbits(32) != NSV_HDR_DWORD) + { + bs.seek(-s); + return -1; + }else{ + glCounterNSVf++; + } + s+=32; + unsigned int headersize=bs.getbits(32); + + if (headersize >= 0x20000000) + { + bs.seek(-s); + return -1; + } + if ((unsigned int)bs.avail() < (headersize-4)*8) + { + int l=headersize-4- (int)(bs.avail()/8); + bs.seek(-s); + return l; + } + + s+=32; + unsigned int lenbytes=bs.getbits(32); + s+=32; + unsigned int lenms=bs.getbits(32); + s+=32; + unsigned int metadatalen=bs.getbits(32); + s+=32; + unsigned int tocalloc=bs.getbits(32); + s+=32; + unsigned int tocsize=bs.getbits(32); + + if (tocalloc < tocsize || lenbytes < headersize || tocalloc + metadatalen + s/8 > headersize) + { + bs.seek(-s); + return -1; + } + + void *metadata=NULL; + + if (metadatalen) + { + if (metadatalen > (SIZE_MAX/8)) + { + bs.seek(-s); + return -1; + } + metadata=malloc(metadatalen+1); + if (!metadata) + { + bs.seek(-s); + return -1; + } + s+=metadatalen*8; + bs.getdata(metadatalen*8,metadata); + ((char*)metadata)[metadatalen]=0; + } + + unsigned int *toc=NULL; + unsigned int *toc_ex=NULL; + + if (tocalloc && tocsize < (SIZE_MAX/8)) + { + toc=(unsigned int *)malloc(tocsize * 4 * 2); + if (!toc) + { + free(metadata); + bs.seek(-s); + return -1; + } + unsigned int x; + int bitsread=0; + for (x = 0; x < tocsize; x ++) { toc[x] = bs.getbits(32); bitsread += 32; } + + if (tocalloc > tocsize*2) + { + bitsread += 32; + if (bs.getbits(32) == NSV_MAKETYPE('T','O','C','2')) + { + toc_ex=toc + tocsize; + for (x = 0; x < tocsize; x ++) { toc_ex[x] = bs.getbits(32); bitsread += 32; } + } + } + bs.seek((tocalloc-tocsize)*32 - bitsread); + s+=tocalloc*32; + } + + hdr->header_size=headersize; + if (lenbytes == 0xFFFFFFFF) + hdr->file_lenbytes=lenbytes; + else + hdr->file_lenbytes=lenbytes-headersize; + hdr->file_lenms=lenms; + hdr->metadata=metadata; + hdr->metadata_len=metadatalen; + hdr->toc=toc; + hdr->toc_ex=toc_ex; + hdr->toc_alloc=tocalloc; + hdr->toc_size=tocsize; + + return 0; +} + +char *nsv_getmetadata(void *metadata, char *name) +{ + if (!metadata) return NULL; + char *p=(char*)metadata; + size_t ln=strlen(name); + for (;;) + { + while (p && (*p == ' ' || *p == '\t' || *p == '\n' || *p == '\r')) p++; + if (p || !*p) break; + if (!_strnicmp(p,name,ln) && p[ln]=='=' && p[ln+1] && p[ln+2]) + { + int cnt=0; + char *np=p+ln+1; + char c=*np++; + while (np[cnt] && np[cnt] != c) cnt++; + + char *s=(char*)malloc(cnt+1); + if (!s) return NULL; + memcpy(s,np,cnt); + s[cnt]=0; + return s; + } + + // advance to next item + while (p && *p && *p != '=') p++; + if (!*p++) break; + if (!*p) break; + char c=*p++; + while (p && *p && *p != c) p++; + if (*p) p++; + } + return NULL; +} |