Merge commit '7621e2f8dec938cf48181c8b10afc9b01f444e68' into beta

This commit is contained in:
Ilya Laktyushin
2025-12-06 02:17:48 +04:00
commit 8344b97e03
28070 changed files with 7995182 additions and 0 deletions
@@ -0,0 +1,245 @@
/* Copyright (C)2012 Xiph.Org Foundation
Copyright (C)2012 Gregory Maxwell
Copyright (C)2012 Jean-Marc Valin
File: diag_range.c
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#ifdef _WIN32
#define I64FORMAT "I64d"
#define I64uFORMAT "I64u"
#else
#define I64FORMAT "lld"
#define I64uFORMAT "llu"
#endif
#include <stdio.h>
#include <opus/opus.h>
#include "diag_range.h"
/*This is some non-exported code copied wholesale from libopus.
*Normal programs shouldn't need these functions, but we use them here
*to parse deep inside multichannel packets in order to get diagnostic
*data for save-range. If you're thinking about copying it and you aren't
*making an opus stream diagnostic tool, you're probably doing something
*wrong.*/
static int parse_size(const unsigned char *data, opus_int32 len, short *size)
{
if (len<1)
{
*size = -1;
return -1;
} else if (data[0]<252)
{
*size = data[0];
return 1;
} else if (len<2)
{
*size = -1;
return -1;
} else {
*size = 4*data[1] + data[0];
return 2;
}
}
static int opus_packet_parse_impl(const unsigned char *data, opus_int32 len,
int self_delimited, unsigned char *out_toc,
const unsigned char *frames[48], short size[48], int *payload_offset)
{
int i, bytes;
int count;
int cbr;
unsigned char ch, toc;
int framesize;
int last_size;
const unsigned char *data0 = data;
if (size==NULL)
return OPUS_BAD_ARG;
framesize = opus_packet_get_samples_per_frame(data, 48000);
cbr = 0;
toc = *data++;
len--;
last_size = len;
switch (toc&0x3)
{
/* One frame */
case 0:
count=1;
break;
/* Two CBR frames */
case 1:
count=2;
cbr = 1;
if (!self_delimited)
{
if (len&0x1)
return OPUS_INVALID_PACKET;
size[0] = last_size = len/2;
}
break;
/* Two VBR frames */
case 2:
count = 2;
bytes = parse_size(data, len, size);
len -= bytes;
if (size[0]<0 || size[0] > len)
return OPUS_INVALID_PACKET;
data += bytes;
last_size = len-size[0];
break;
/* Multiple CBR/VBR frames (from 0 to 120 ms) */
case 3:
if (len<1)
return OPUS_INVALID_PACKET;
/* Number of frames encoded in bits 0 to 5 */
ch = *data++;
count = ch&0x3F;
if (count <= 0 || framesize*count > 5760)
return OPUS_INVALID_PACKET;
len--;
/* Padding flag is bit 6 */
if (ch&0x40)
{
int padding=0;
int p;
do {
if (len<=0)
return OPUS_INVALID_PACKET;
p = *data++;
len--;
padding += p==255 ? 254: p;
} while (p==255);
len -= padding;
}
if (len<0)
return OPUS_INVALID_PACKET;
/* VBR flag is bit 7 */
cbr = !(ch&0x80);
if (!cbr)
{
/* VBR case */
last_size = len;
for (i=0;i<count-1;i++)
{
bytes = parse_size(data, len, size+i);
len -= bytes;
if (size[i]<0 || size[i] > len)
return OPUS_INVALID_PACKET;
data += bytes;
last_size -= bytes+size[i];
}
if (last_size<0)
return OPUS_INVALID_PACKET;
} else if (!self_delimited)
{
/* CBR case */
last_size = len/count;
if (last_size*count!=len)
return OPUS_INVALID_PACKET;
for (i=0;i<count-1;i++)
size[i] = last_size;
}
break;
}
/* Self-delimited framing has an extra size for the last frame. */
if (self_delimited)
{
bytes = parse_size(data, len, size+count-1);
len -= bytes;
if (size[count-1]<0 || size[count-1] > len)
return OPUS_INVALID_PACKET;
data += bytes;
/* For CBR packets, apply the size to all the frames. */
if (cbr)
{
if (size[count-1]*count > len)
return OPUS_INVALID_PACKET;
for (i=0;i<count-1;i++)
size[i] = size[count-1];
} else if(size[count-1] > last_size)
return OPUS_INVALID_PACKET;
} else
{
/* Because it's not encoded explicitly, it's possible the size of the
last packet (or all the packets, for the CBR case) is larger than
1275. Reject them here.*/
if (last_size > 1275)
return OPUS_INVALID_PACKET;
size[count-1] = last_size;
}
if (frames)
{
for (i=0;i<count;i++)
{
frames[i] = data;
data += size[i];
}
}
if (out_toc)
*out_toc = toc;
if (payload_offset)
*payload_offset = data-data0;
return count;
}
void save_range(FILE *frange, int frame_size, unsigned char *packet, int nbBytes, opus_uint32 *rngs, int nb_streams){
int i, parsed_size;
const unsigned char *subpkt;
static const char *bw_strings[5]={"NB","MB","WB","SWB","FB"};
static const char *mode_strings[3]={"LP","HYB","MDCT"};
fprintf(frange,"%d, %d, ",frame_size,nbBytes);
subpkt=packet;
parsed_size=nbBytes;
for(i=0;i<nb_streams;i++){
int j,payload_offset,nf;
const unsigned char *frames[48];
unsigned char toc;
short size[48];
payload_offset=0;
nf=opus_packet_parse_impl(subpkt,parsed_size,i+1!=nb_streams,
&toc,frames,size,&payload_offset);
fprintf(frange,"[[%d",(int)(frames[0]-subpkt));
for(j=0;j<nf;j++)fprintf(frange,", %d",size[j]);
fprintf(frange,"], %s, %s, %c, %d",
mode_strings[((((subpkt[0]>>3)+48)&92)+4)>>5],
bw_strings[opus_packet_get_bandwidth(subpkt)-OPUS_BANDWIDTH_NARROWBAND],
subpkt[0]&4?'S':'M',opus_packet_get_samples_per_frame(subpkt,48000));
fprintf(frange,", %" I64uFORMAT "]%s",(unsigned long long)rngs[i],i+1==nb_streams?"\n":", ");
parsed_size-=payload_offset;
subpkt+=payload_offset;
}
}
@@ -0,0 +1,28 @@
/* Copyright (C)2012 Xiph.Org Foundation
File: diag_range.h
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
void save_range(FILE *frange, int frame_size, unsigned char *packet, int nbBytes, opus_uint32 *rngs, int nb_streams);
@@ -0,0 +1,286 @@
/* Copyright (C)2012 Xiph.Org Foundation
File: opus_header.c
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include "opus_header.h"
#include <string.h>
#include <stdio.h>
/* Header contents:
- "OpusHead" (64 bits)
- version number (8 bits)
- Channels C (8 bits)
- Pre-skip (16 bits)
- Sampling rate (32 bits)
- Gain in dB (16 bits, S7.8)
- Mapping (8 bits, 0=single stream (mono/stereo) 1=Vorbis mapping,
2..254: reserved, 255: multistream with no mapping)
- if (mapping != 0)
- N = totel number of streams (8 bits)
- M = number of paired streams (8 bits)
- C times channel origin
- if (C<2*M)
- stream = byte/2
- if (byte&0x1 == 0)
- left
else
- right
- else
- stream = byte-M
*/
typedef struct {
unsigned char *data;
int maxlen;
int pos;
} Packet;
typedef struct {
const unsigned char *data;
int maxlen;
int pos;
} ROPacket;
static int write_uint32(Packet *p, ogg_uint32_t val)
{
if (p->pos>p->maxlen-4)
return 0;
p->data[p->pos ] = (val ) & 0xFF;
p->data[p->pos+1] = (val>> 8) & 0xFF;
p->data[p->pos+2] = (val>>16) & 0xFF;
p->data[p->pos+3] = (val>>24) & 0xFF;
p->pos += 4;
return 1;
}
static int write_uint16(Packet *p, ogg_uint16_t val)
{
if (p->pos>p->maxlen-2)
return 0;
p->data[p->pos ] = (val ) & 0xFF;
p->data[p->pos+1] = (val>> 8) & 0xFF;
p->pos += 2;
return 1;
}
static int write_chars(Packet *p, const unsigned char *str, int nb_chars)
{
int i;
if (p->pos>p->maxlen-nb_chars)
return 0;
for (i=0;i<nb_chars;i++)
p->data[p->pos++] = str[i];
return 1;
}
static int read_uint32(ROPacket *p, ogg_uint32_t *val)
{
if (p->pos>p->maxlen-4)
return 0;
*val = (ogg_uint32_t)p->data[p->pos ];
*val |= (ogg_uint32_t)p->data[p->pos+1]<< 8;
*val |= (ogg_uint32_t)p->data[p->pos+2]<<16;
*val |= (ogg_uint32_t)p->data[p->pos+3]<<24;
p->pos += 4;
return 1;
}
static int read_uint16(ROPacket *p, ogg_uint16_t *val)
{
if (p->pos>p->maxlen-2)
return 0;
*val = (ogg_uint16_t)p->data[p->pos ];
*val |= (ogg_uint16_t)p->data[p->pos+1]<<8;
p->pos += 2;
return 1;
}
static int read_chars(ROPacket *p, unsigned char *str, int nb_chars)
{
int i;
if (p->pos>p->maxlen-nb_chars)
return 0;
for (i=0;i<nb_chars;i++)
str[i] = p->data[p->pos++];
return 1;
}
int opus_header_parse(const unsigned char *packet, int len, OpusHeader *h)
{
int i;
char str[9];
ROPacket p;
unsigned char ch;
ogg_uint16_t shortval;
p.data = packet;
p.maxlen = len;
p.pos = 0;
str[8] = 0;
if (len<19)return 0;
read_chars(&p, (unsigned char*)str, 8);
if (memcmp(str, "OpusHead", 8)!=0)
return 0;
if (!read_chars(&p, &ch, 1))
return 0;
h->version = ch;
if((h->version&240) != 0) /* Only major version 0 supported. */
return 0;
if (!read_chars(&p, &ch, 1))
return 0;
h->channels = ch;
if (h->channels == 0)
return 0;
if (!read_uint16(&p, &shortval))
return 0;
h->preskip = shortval;
if (!read_uint32(&p, &h->input_sample_rate))
return 0;
if (!read_uint16(&p, &shortval))
return 0;
h->gain = (short)shortval;
if (!read_chars(&p, &ch, 1))
return 0;
h->channel_mapping = ch;
if (h->channel_mapping != 0)
{
if (!read_chars(&p, &ch, 1))
return 0;
if (ch<1)
return 0;
h->nb_streams = ch;
if (!read_chars(&p, &ch, 1))
return 0;
if (ch>h->nb_streams || (ch+h->nb_streams)>255)
return 0;
h->nb_coupled = ch;
/* Multi-stream support */
for (i=0;i<h->channels;i++)
{
if (!read_chars(&p, &h->stream_map[i], 1))
return 0;
if (h->stream_map[i]>(h->nb_streams+h->nb_coupled) && h->stream_map[i]!=255)
return 0;
}
} else {
if(h->channels>2)
return 0;
h->nb_streams = 1;
h->nb_coupled = h->channels>1;
h->stream_map[0]=0;
h->stream_map[1]=1;
}
/*For version 0/1 we know there won't be any more data
so reject any that have data past the end.*/
if ((h->version==0 || h->version==1) && p.pos != len)
return 0;
return 1;
}
int opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len)
{
int i;
Packet p;
unsigned char ch;
p.data = packet;
p.maxlen = len;
p.pos = 0;
if (len<19)return 0;
if (!write_chars(&p, (const unsigned char*)"OpusHead", 8))
return 0;
/* Version is 1 */
ch = 1;
if (!write_chars(&p, &ch, 1))
return 0;
ch = h->channels;
if (!write_chars(&p, &ch, 1))
return 0;
if (!write_uint16(&p, h->preskip))
return 0;
if (!write_uint32(&p, h->input_sample_rate))
return 0;
if (!write_uint16(&p, h->gain))
return 0;
ch = h->channel_mapping;
if (!write_chars(&p, &ch, 1))
return 0;
if (h->channel_mapping != 0)
{
ch = h->nb_streams;
if (!write_chars(&p, &ch, 1))
return 0;
ch = h->nb_coupled;
if (!write_chars(&p, &ch, 1))
return 0;
/* Multi-stream support */
for (i=0;i<h->channels;i++)
{
if (!write_chars(&p, &h->stream_map[i], 1))
return 0;
}
}
return p.pos;
}
/* This is just here because it's a convenient file linked by both opusenc and
opusdec (to guarantee this maps stays in sync). */
const int wav_permute_matrix[8][8] =
{
{0}, /* 1.0 mono */
{0,1}, /* 2.0 stereo */
{0,2,1}, /* 3.0 channel ('wide') stereo */
{0,1,2,3}, /* 4.0 discrete quadraphonic */
{0,2,1,3,4}, /* 5.0 surround */
{0,2,1,4,5,3}, /* 5.1 surround */
{0,2,1,5,6,4,3}, /* 6.1 surround */
{0,2,1,6,7,4,5,3} /* 7.1 surround (classic theater 8-track) */
};
@@ -0,0 +1,51 @@
/* Copyright (C)2012 Xiph.Org Foundation
File: opus_header.h
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef OPUS_HEADER_H
#define OPUS_HEADER_H
#include <ogg/ogg.h>
typedef struct {
int version;
int channels; /* Number of channels: 1..255 */
int preskip;
ogg_uint32_t input_sample_rate;
int gain; /* in dB S7.8 should be zero whenever possible */
int channel_mapping;
/* The rest is only used if channel_mapping != 0 */
int nb_streams;
int nb_coupled;
unsigned char stream_map[255];
} OpusHeader;
int opus_header_parse(const unsigned char *header, int len, OpusHeader *h);
int opus_header_to_packet(const OpusHeader *h, unsigned char *packet, int len);
extern const int wav_permute_matrix[8][8];
#endif
@@ -0,0 +1,865 @@
#include <opus/opus_types.h>
#include <ogg/ogg.h>
#import "TGDataItem.h"
#import "TGOggOpusWriter.h"
#ifdef ENABLE_NLS
#include <libintl.h>
#define _(X) gettext(X)
#else
#define _(X) (X)
#define textdomain(X)
#define bindtextdomain(X, Y)
#endif
#ifdef gettext_noop
#define N_(X) gettext_noop(X)
#else
#define N_(X) (X)
#endif
typedef struct
{
void *readdata;
opus_int64 total_samples_per_channel;
int rawmode;
int channels;
long rate;
int gain;
int samplesize;
int endianness;
char *infilename;
int ignorelength;
int skip;
int extraout;
char *comments;
int comments_length;
int copy_comments;
} oe_enc_opt;
typedef struct
{
int (*id_func)(unsigned char *buf, int len); /* Returns true if can load file */
int id_data_len; /* Amount of data needed to id whether this can load the file */
int (*open_func)(FILE *in, oe_enc_opt *opt, unsigned char *buf, int buflen);
void (*close_func)(void *);
char *format;
char *description;
} input_format;
#include <opus/opus.h>
#include <ogg/ogg.h>
#include "opus_header.h"
static bool comment_init(char **comments, int* length, const char *vendor_string);
static bool comment_add(char **comments, int* length, char *tag, char *val);
static bool comment_pad(char **comments, int* length, int amount);
static inline int writeOggPage(ogg_page *page, TGDataItem *fileItem)
{
int written = (int)(page->header_len + page->body_len);
NSMutableData *data = [[NSMutableData alloc] init];
[data appendBytes:page->header length:page->header_len];
[data appendBytes:page->body length:page->body_len];
[fileItem appendData:data];
return MAX(0, written);
}
@interface TGOggOpusWriter ()
{
TGDataItem *_dataItem;
OpusEncoder *_encoder;
uint8_t *_packet;
oe_enc_opt inopt;
ogg_stream_state os;
ogg_page og;
ogg_packet op;
ogg_int64_t last_granulepos;
ogg_int64_t enc_granulepos;
int last_segments;
int eos;
OpusHeader header;
ogg_int32_t _packetId;
int size_segments;
opus_int64 nb_encoded;
opus_int64 bytes_written;
opus_int64 pages_out;
opus_int64 total_bytes;
opus_int64 total_samples;
opus_int32 nb_samples;
opus_int32 peak_bytes;
opus_int32 min_bytes;
int max_frame_bytes;
opus_int32 bitrate;
opus_int32 rate;
opus_int32 coding_rate;
opus_int32 frame_size;
int with_cvbr;
int max_ogg_delay;
int comment_padding;
int serialno;
opus_int32 lookahead;
}
@property (nonatomic) ogg_sync_state syncState;
@end
@implementation TGOggOpusWriter
- (instancetype)init
{
self = [super init];
if (self != nil)
{
bitrate = 30 * 1024;
rate = 48000;
coding_rate = 48000;
frame_size = 960;
with_cvbr = 1;
max_ogg_delay = 48000;
comment_padding = 512;
_packetId = -1;
}
return self;
}
- (void)dealloc {
[self cleanup];
}
- (void)cleanup
{
if (_encoder != NULL)
{
opus_encoder_destroy(_encoder);
_encoder = NULL;
}
ogg_stream_clear(&os);
if (_packet != NULL)
{
free(_packet);
_packet = NULL;
}
}
- (bool)beginWithDataItem:(TGDataItem *)dataItem
{
_dataItem = dataItem;
inopt.channels = 1;
inopt.rate = coding_rate=rate;
inopt.gain = 0;
inopt.samplesize = 16;
inopt.endianness = 0;
inopt.rawmode = 0;
inopt.ignorelength = 0;
inopt.copy_comments = 0;
arc4random_buf(&serialno, sizeof(serialno));
const char *opus_version = opus_get_version_string();
comment_init(&inopt.comments, &inopt.comments_length, opus_version);
// bitrate = 16 * 1024;
// inopt.rawmode = 1;
// inopt.ignorelength = 1;
// inopt.samplesize = 16;
// inopt.rate = 16000;
// inopt.channels = 1;
rate = (opus_int32)inopt.rate;
inopt.skip = 0;
// In order to code the complete length we'll need to do a little padding
//setup_padder(&inopt, &original_samples);
if (rate > 24000)
coding_rate = 48000;
else if (rate > 16000)
coding_rate = 24000;
else if (rate > 12000)
coding_rate = 16000;
else if (rate > 8000)
coding_rate = 12000;
else
coding_rate = 8000;
// Scale the resampler complexity, but only for 48000 output because the near-cutoff behavior matters a lot more at lower rates
if (rate != coding_rate)
{
NSLog(@"Invalid rate");
return false;
}
header.channels = 1;
header.channel_mapping = 0;
header.input_sample_rate = rate;
header.gain = inopt.gain;
header.nb_streams = 1;
int result = OPUS_OK;
_encoder = opus_encoder_create(coding_rate, 1, OPUS_APPLICATION_AUDIO, &result);
if (result != OPUS_OK)
{
NSLog(@"Error cannot create encoder: %s", opus_strerror(result));
return false;
}
min_bytes = max_frame_bytes = (1275 * 3 + 7) * header.nb_streams;
_packet = malloc(max_frame_bytes);
result = opus_encoder_ctl(_encoder, OPUS_SET_BITRATE(bitrate));
if (result != OPUS_OK)
{
NSLog(@"Error OPUS_SET_BITRATE returned: %s", opus_strerror(result));
return false;
}
/*result = opus_encoder_ctl(_encoder, OPUS_SET_VBR(1));
if (result != OPUS_OK)
{
NSLog(@"Error OPUS_SET_VBR returned: %s", opus_strerror(result));
return false;
}*/
/*ret = opus_multistream_encoder_ctl(st, OPUS_SET_VBR_CONSTRAINT(1));
if (ret != OPUS_OK)
{
NSLog(@"Error OPUS_SET_VBR_CONSTRAINT returned: %s", opus_strerror(ret));
return false;
}*/
/*ret = opus_multistream_encoder_ctl(st, OPUS_SET_COMPLEXITY(complexity));
if(ret != OPUS_OK)
{
NSLog(@"Error OPUS_SET_COMPLEXITY returned: %s", opus_strerror(ret));
return false;
}*/
/*result = opus_encoder_ctl(st, OPUS_SET_PACKET_LOSS_PERC(expect_loss));
if (ret != OPUS_OK)
{
NSLog(@"Error OPUS_SET_PACKET_LOSS_PERC returned: %s", opus_strerror(ret));
return false;
}*/
#ifdef OPUS_SET_LSB_DEPTH
result = opus_encoder_ctl(_encoder, OPUS_SET_LSB_DEPTH(MAX(8, MIN(24, inopt.samplesize))));
if (result != OPUS_OK)
{
NSLog(@"Warning OPUS_SET_LSB_DEPTH returned: %s", opus_strerror(result));
}
#endif
// We do the lookahead check late so user CTLs can change it
result = opus_encoder_ctl(_encoder, OPUS_GET_LOOKAHEAD(&lookahead));
if (result != OPUS_OK)
{
NSLog(@"Error OPUS_GET_LOOKAHEAD returned: %s", opus_strerror(result));
return false;
}
inopt.skip += lookahead;
// Regardless of the rate we're coding at the ogg timestamping/skip is always timed at 48000.
header.preskip = (int)(inopt.skip * (48000.0 / coding_rate));
// Extra samples that need to be read to compensate for the pre-skip
inopt.extraout = (int)(header.preskip * (rate / 48000.0));
// Initialize Ogg stream struct
if (ogg_stream_init(&os, serialno) == -1)
{
NSLog(@"Error: stream init failed");
return false;
}
// Write header
{
unsigned char header_data[100];
int packet_size = opus_header_to_packet(&header, header_data, 100);
op.packet = header_data;
op.bytes = packet_size;
op.b_o_s = 1;
op.e_o_s = 0;
op.granulepos = 0;
op.packetno = 0;
ogg_stream_packetin(&os, &op);
while ((result = ogg_stream_flush(&os, &og)))
{
if (!result)
break;
int pageBytesWritten = writeOggPage(&og, _dataItem);
if (pageBytesWritten != og.header_len + og.body_len)
{
NSLog(@"Error: failed writing header to output stream");
return false;
}
bytes_written += pageBytesWritten;
pages_out++;
}
comment_pad(&inopt.comments, &inopt.comments_length, comment_padding);
op.packet = (unsigned char *)inopt.comments;
op.bytes = inopt.comments_length;
op.b_o_s = 0;
op.e_o_s = 0;
op.granulepos = 0;
op.packetno = 1;
ogg_stream_packetin(&os, &op);
}
// Writing the rest of the opus header packets
while ((result = ogg_stream_flush(&os, &og)))
{
if (result == 0)
break;
int writtenPageBytes = writeOggPage(&og, _dataItem);
if (writtenPageBytes != og.header_len + og.body_len)
{
NSLog(@"Error: failed writing header to output stream");
return false;
}
bytes_written += writtenPageBytes;
pages_out++;
}
free(inopt.comments);
return true;
}
- (bool)parseExistingOpusFile:(NSData *)data
{
ogg_sync_init(&_syncState);
char *buffer = ogg_sync_buffer(&_syncState, (long)data.length);
memcpy(buffer, data.bytes, data.length);
ogg_sync_wrote(&_syncState, (long)data.length);
ogg_stream_state tempStream;
ogg_page page;
ogg_packet packet;
bool headerParsed = false;
bool foundStream = false;
ogg_int64_t finalGranulePos = 0;
while (ogg_sync_pageout(&_syncState, &page) == 1) {
if (!foundStream) {
serialno = ogg_page_serialno(&page);
if (ogg_stream_init(&tempStream, serialno) != 0) {
ogg_sync_clear(&_syncState);
return false;
}
foundStream = true;
}
if (ogg_page_serialno(&page) == serialno) {
ogg_stream_pagein(&tempStream, &page);
if (ogg_page_granulepos(&page) != -1) {
finalGranulePos = ogg_page_granulepos(&page);
}
while (ogg_stream_packetout(&tempStream, &packet) == 1) {
if (!headerParsed && packet.packetno == 0) {
if (![self parseOpusHeader:packet.packet length:packet.bytes]) {
ogg_stream_clear(&tempStream);
ogg_sync_clear(&_syncState);
return false;
}
headerParsed = true;
}
_packetId = (ogg_int32_t)packet.packetno;
if (packet.granulepos != -1) {
enc_granulepos = packet.granulepos;
last_granulepos = packet.granulepos;
finalGranulePos = packet.granulepos;
}
}
}
}
if (finalGranulePos > header.preskip) {
opus_int64 samples = finalGranulePos - header.preskip;
total_samples = (samples * rate) / 48000;
} else {
total_samples = 0;
}
ogg_stream_clear(&tempStream);
ogg_sync_clear(&_syncState);
if (!headerParsed) {
return false;
}
return true;
}
- (bool)parseOpusHeader:(unsigned char *)data length:(long)length
{
if (length < 19) {
NSLog(@"Opus header too short");
return false;
}
if (memcmp(data, "OpusHead", 8) != 0) {
NSLog(@"Invalid Opus header signature");
return false;
}
header.channels = data[9];
header.preskip = data[10] | (data[11] << 8);
header.input_sample_rate = data[12] | (data[13] << 8) | (data[14] << 16) | (data[15] << 24);
header.gain = (signed short)(data[16] | (data[17] << 8));
header.channel_mapping = data[18];
if (header.channels == 0) {
return false;
}
rate = header.input_sample_rate;
coding_rate = rate;
if (rate > 24000)
coding_rate = 48000;
else if (rate > 16000)
coding_rate = 24000;
else if (rate > 12000)
coding_rate = 16000;
else if (rate > 8000)
coding_rate = 12000;
else
coding_rate = 8000;
header.nb_streams = 1;
return true;
}
- (bool)initializeEncoderForAppend
{
bytes_written = _dataItem.data.length;
inopt.channels = header.channels;
inopt.rate = rate;
inopt.gain = header.gain;
inopt.samplesize = 16;
inopt.endianness = 0;
inopt.rawmode = 0;
inopt.ignorelength = 0;
inopt.copy_comments = 0;
int result = OPUS_OK;
_encoder = opus_encoder_create(coding_rate, header.channels, OPUS_APPLICATION_AUDIO, &result);
if (result != OPUS_OK) {
NSLog(@"Error cannot create encoder: %s", opus_strerror(result));
return false;
}
bitrate = 30 * 1024;
frame_size = 960;
opus_encoder_ctl(_encoder, OPUS_SET_BITRATE(bitrate));
#ifdef OPUS_SET_LSB_DEPTH
opus_encoder_ctl(_encoder, OPUS_SET_LSB_DEPTH(16));
#endif
opus_encoder_ctl(_encoder, OPUS_GET_LOOKAHEAD(&lookahead));
if (ogg_stream_init(&os, serialno) == -1) {
NSLog(@"Error: stream init failed");
return false;
}
max_frame_bytes = (1275 * 3 + 7) * header.nb_streams;
_packet = malloc(max_frame_bytes);
return true;
}
- (bool)beginAppendWithDataItem:(TGDataItem *)dataItem
{
if (dataItem.data.length == 0) {
return [self beginWithDataItem:dataItem];
}
_dataItem = dataItem;
if (![self parseExistingOpusFile:_dataItem.data]) {
return false;
}
return [self initializeEncoderForAppend];
}
- (bool)writeFrame:(uint8_t *)framePcmBytes frameByteCount:(NSUInteger)frameByteCount
{
// Main encoding loop (one frame per iteration)
nb_samples = -1;
int cur_frame_size = frame_size;
_packetId++;
if (nb_samples < 0)
{
nb_samples = (opus_int32)(frameByteCount / 2);
total_samples += nb_samples;
if (nb_samples < frame_size)
op.e_o_s = 1;
else
op.e_o_s = 0;
}
op.e_o_s |= eos;
int nbBytes = 0;
if (nb_samples != 0)
{
uint8_t *paddedFrameBytes = framePcmBytes;
bool freePaddedFrameBytes = false;
if (nb_samples < cur_frame_size)
{
paddedFrameBytes = malloc(cur_frame_size * 2);
freePaddedFrameBytes = true;
memcpy(paddedFrameBytes, framePcmBytes, frameByteCount);
memset(paddedFrameBytes + nb_samples * 2, 0, cur_frame_size * 2 - nb_samples * 2);
}
// Encode current frame
nbBytes = opus_encode(_encoder, (opus_int16 *)paddedFrameBytes, cur_frame_size, _packet, max_frame_bytes / 10);
if (freePaddedFrameBytes)
{
free(paddedFrameBytes);
paddedFrameBytes = NULL;
}
if (nbBytes < 0)
{
NSLog(@"Encoding failed: %s. Aborting.", opus_strerror(nbBytes));
return false;
}
nb_encoded += cur_frame_size;
enc_granulepos += cur_frame_size * 48000 / coding_rate;
total_bytes += nbBytes;
size_segments = (nbBytes + 255) / 255;
peak_bytes = MAX(nbBytes, peak_bytes);
min_bytes = MIN(nbBytes, min_bytes);
}
// Flush early if adding this packet would make us end up with a continued page which we wouldn't have otherwise
while ((((size_segments<=255)&&(last_segments+size_segments>255)) ||
(enc_granulepos-last_granulepos>max_ogg_delay)) &&
ogg_stream_flush_fill(&os, &og, 255 * 255))
{
if (ogg_page_packets(&og) != 0)
last_granulepos = ogg_page_granulepos(&og);
last_segments -= og.header[26];
int writtenPageBytes = writeOggPage(&og, _dataItem);
if (writtenPageBytes != og.header_len + og.body_len)
{
NSLog(@"Error: failed writing data to output stream");
return false;
}
bytes_written += writtenPageBytes;
pages_out++;
}
if (framePcmBytes != NULL) {
op.packet = (unsigned char *)_packet;
op.bytes = nbBytes;
op.b_o_s = 0;
op.granulepos = enc_granulepos;
if (op.e_o_s)
{
/* We compute the final GP as ceil(len*48k/input_rate). When a resampling
decoder does the matching floor(len*input/48k) conversion the length will
be exactly the same as the input.
*/
op.granulepos = ((total_samples * 48000 + rate - 1) / rate) + header.preskip;
}
op.packetno = 2 + _packetId;
ogg_stream_packetin(&os, &op);
last_segments += size_segments;
}
// If the stream is over or we're sure that the delayed flush will fire, go ahead and flush now to avoid adding delay
while ((op.e_o_s || (enc_granulepos + (frame_size * 48000 / coding_rate) - last_granulepos > max_ogg_delay) ||
(last_segments >= 255)) ? ogg_stream_flush_fill(&os, &og, 255 * 255) : ogg_stream_pageout_fill(&os, &og, 255 * 255))
{
if (ogg_page_packets(&og) != 0)
last_granulepos = ogg_page_granulepos(&og);
last_segments -= og.header[26];
int writtenPageBytes = writeOggPage(&og, _dataItem);
if (writtenPageBytes != og.header_len + og.body_len)
{
NSLog(@"Error: failed writing data to output stream");
return false;
}
bytes_written += writtenPageBytes;
pages_out++;
}
return true;
}
- (NSUInteger)encodedBytes
{
return (NSUInteger)bytes_written;
}
- (NSTimeInterval)encodedDuration
{
return total_samples / (NSTimeInterval)coding_rate;
}
- (NSDictionary *)pause
{
[self flushPages];
return [self saveState];
}
- (bool)resumeWithDataItem:(TGDataItem *)dataItem encoderState:(NSDictionary *)state
{
if (![self restoreState:state withDataItem:dataItem])
return false;
_packetId++;
return true;
}
- (bool)flushPages
{
while (ogg_stream_flush_fill(&os, &og, 255 * 255))
{
if (ogg_page_packets(&og) != 0)
last_granulepos = ogg_page_granulepos(&og);
last_segments -= og.header[26];
int writtenPageBytes = writeOggPage(&og, _dataItem);
if (writtenPageBytes != og.header_len + og.body_len)
{
NSLog(@"Error: failed writing data to output stream");
return false;
}
bytes_written += writtenPageBytes;
pages_out++;
}
return true;
}
- (NSDictionary *)saveState
{
NSMutableDictionary *state = [NSMutableDictionary dictionary];
[state setObject:@(_packetId) forKey:@"packetId"];
[state setObject:@(enc_granulepos) forKey:@"enc_granulepos"];
[state setObject:@(last_granulepos) forKey:@"last_granulepos"];
[state setObject:@(last_segments) forKey:@"last_segments"];
[state setObject:@(nb_encoded) forKey:@"nb_encoded"];
[state setObject:@(bytes_written) forKey:@"bytes_written"];
[state setObject:@(pages_out) forKey:@"pages_out"];
[state setObject:@(total_bytes) forKey:@"total_bytes"];
[state setObject:@(total_samples) forKey:@"total_samples"];
[state setObject:@(serialno) forKey:@"serialno"];
[state setObject:@(rate) forKey:@"rate"];
[state setObject:@(coding_rate) forKey:@"coding_rate"];
[state setObject:@(frame_size) forKey:@"frame_size"];
[state setObject:@(bitrate) forKey:@"bitrate"];
[state setObject:@(with_cvbr) forKey:@"with_cvbr"];
[state setObject:@(lookahead) forKey:@"lookahead"];
NSDictionary *headerDict = @{
@"channels": @(header.channels),
@"channel_mapping": @(header.channel_mapping),
@"input_sample_rate": @(header.input_sample_rate),
@"gain": @(header.gain),
@"nb_streams": @(header.nb_streams),
@"preskip": @(header.preskip)
};
[state setObject:headerDict forKey:@"header"];
return state;
}
- (bool)restoreState:(NSDictionary *)state withDataItem:(TGDataItem *)dataItem
{
if (state == nil || dataItem == nil)
return false;
[self cleanup];
_dataItem = dataItem;
_packetId = [state[@"packetId"] intValue];
enc_granulepos = [state[@"enc_granulepos"] longLongValue];
last_granulepos = [state[@"last_granulepos"] longLongValue];
last_segments = [state[@"last_segments"] intValue];
nb_encoded = [state[@"nb_encoded"] longLongValue];
bytes_written = [state[@"bytes_written"] longLongValue];
pages_out = [state[@"pages_out"] longLongValue];
total_bytes = [state[@"total_bytes"] longLongValue];
total_samples = [state[@"total_samples"] longLongValue];
serialno = [state[@"serialno"] intValue];
rate = [state[@"rate"] intValue];
coding_rate = [state[@"coding_rate"] intValue];
frame_size = [state[@"frame_size"] intValue];
bitrate = [state[@"bitrate"] intValue];
with_cvbr = [state[@"with_cvbr"] intValue];
lookahead = [state[@"lookahead"] intValue];
NSDictionary *headerDict = state[@"header"];
header.channels = [headerDict[@"channels"] intValue];
header.channel_mapping = [headerDict[@"channel_mapping"] intValue];
header.input_sample_rate = [headerDict[@"input_sample_rate"] intValue];
header.gain = [headerDict[@"gain"] intValue];
header.nb_streams = [headerDict[@"nb_streams"] intValue];
header.preskip = [headerDict[@"preskip"] intValue];
int result = OPUS_OK;
_encoder = opus_encoder_create(coding_rate, header.channels, OPUS_APPLICATION_AUDIO, &result);
if (result != OPUS_OK)
{
NSLog(@"Error cannot create encoder: %s", opus_strerror(result));
return false;
}
opus_encoder_ctl(_encoder, OPUS_SET_BITRATE(bitrate));
#ifdef OPUS_SET_LSB_DEPTH
opus_encoder_ctl(_encoder, OPUS_SET_LSB_DEPTH(16));
#endif
if (ogg_stream_init(&os, serialno) == -1)
{
NSLog(@"Error: stream init failed");
return false;
}
min_bytes = max_frame_bytes = (1275 * 3 + 7) * header.nb_streams;
_packet = malloc(max_frame_bytes);
return true;
}
@end
/*
Comments will be stored in the Vorbis style.
It is describled in the "Structure" section of
http://www.xiph.org/ogg/vorbis/doc/v-comment.html
However, Opus and other non-vorbis formats omit the "framing_bit".
The comment header is decoded as follows:
1) [vendor_length] = read an unsigned integer of 32 bits
2) [vendor_string] = read a UTF-8 vector as [vendor_length] octets
3) [user_comment_list_length] = read an unsigned integer of 32 bits
4) iterate [user_comment_list_length] times {
5) [length] = read an unsigned integer of 32 bits
6) this iteration's user comment = read a UTF-8 vector as [length] octets
}
7) done.
*/
#define readint(buf, base) (((buf[base+3]<<24)&0xff000000)| \
((buf[base+2]<<16)&0xff0000)| \
((buf[base+1]<<8)&0xff00)| \
(buf[base]&0xff))
#define writeint(buf, base, val) do{ buf[base+3]=((val)>>24)&0xff; \
buf[base+2]=((val)>>16)&0xff; \
buf[base+1]=((val)>>8)&0xff; \
buf[base]=(val)&0xff; \
}while(0)
static bool comment_init(char **comments, int *length, const char *vendor_string)
{
// The 'vendor' field should be the actual encoding library used
int vendor_length = (int)strlen(vendor_string);
int user_comment_list_length = 0;
int len = 8 + 4 + vendor_length + 4;
char *p = (char *)malloc(len);
memcpy(p, "OpusTags", 8);
writeint(p, 8, vendor_length);
memcpy(p + 12, vendor_string, vendor_length);
writeint(p, 12 + vendor_length, user_comment_list_length);
*length = len;
*comments = p;
return true;
}
__unused bool comment_add(char **comments, int* length, char *tag, char *val)
{
char *p = *comments;
int vendor_length = readint(p, 8);
int user_comment_list_length = readint(p, 8 + 4 + vendor_length);
int tag_len = (tag ? (int)strlen(tag) + 1 : 0);
int val_len = (int)strlen(val);
int len = (*length) + 4 + tag_len + val_len;
p = (char *)realloc(p, len);
writeint(p, *length, tag_len+val_len); /* length of comment */
if (tag)
{
memcpy(p + *length + 4, tag, tag_len); /* comment tag */
(p+*length+4)[tag_len-1] = '='; /* separator */
}
memcpy(p + *length + 4 + tag_len, val, val_len); /* comment */
writeint(p, 8 + 4 + vendor_length, user_comment_list_length + 1);
*comments = p;
*length = len;
return true;
}
static bool comment_pad(char **comments, int* length, int amount)
{
if (amount > 0)
{
char *p = *comments;
// Make sure there is at least amount worth of padding free, and round up to the maximum that fits in the current ogg segments
int newlen = (*length + amount + 255) / 255 * 255 - 1;
p = realloc(p, newlen);
for (int i = *length; i < newlen; i++)
{
p[i] = 0;
}
*comments = p;
*length = newlen;
}
return true;
}
#undef readint
#undef writeint
@@ -0,0 +1,499 @@
/* Copyright (C)2007-2013 Xiph.Org Foundation
File: picture.c
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include "picture.h"
static const char BASE64_TABLE[64]={
'A','B','C','D','E','F','G','H','I','J','K','L','M','N','O','P',
'Q','R','S','T','U','V','W','X','Y','Z','a','b','c','d','e','f',
'g','h','i','j','k','l','m','n','o','p','q','r','s','t','u','v',
'w','x','y','z','0','1','2','3','4','5','6','7','8','9','+','/'
};
/*Utility function for base64 encoding METADATA_BLOCK_PICTURE tags.
Stores BASE64_LENGTH(len)+1 bytes in dst (including a terminating NUL).*/
void base64_encode(char *dst, const char *src, int len){
unsigned s0;
unsigned s1;
unsigned s2;
int ngroups;
int i;
ngroups=len/3;
for(i=0;i<ngroups;i++){
s0=(unsigned char)src[3*i+0];
s1=(unsigned char)src[3*i+1];
s2=(unsigned char)src[3*i+2];
dst[4*i+0]=BASE64_TABLE[s0>>2];
dst[4*i+1]=BASE64_TABLE[(s0&3)<<4|s1>>4];
dst[4*i+2]=BASE64_TABLE[(s1&15)<<2|s2>>6];
dst[4*i+3]=BASE64_TABLE[s2&63];
}
len-=3*i;
if(len==1){
s0=(unsigned char)src[3*i+0];
dst[4*i+0]=BASE64_TABLE[s0>>2];
dst[4*i+1]=BASE64_TABLE[(s0&3)<<4];
dst[4*i+2]='=';
dst[4*i+3]='=';
i++;
}
else if(len==2){
s0=(unsigned char)src[3*i+0];
s1=(unsigned char)src[3*i+1];
dst[4*i+0]=BASE64_TABLE[s0>>2];
dst[4*i+1]=BASE64_TABLE[(s0&3)<<4|s1>>4];
dst[4*i+2]=BASE64_TABLE[(s1&15)<<2];
dst[4*i+3]='=';
i++;
}
dst[4*i]='\0';
}
/*A version of strncasecmp() that is guaranteed to only ignore the case of
ASCII characters.*/
int oi_strncasecmp(const char *a, const char *b, int n){
int i;
for(i=0;i<n;i++){
int aval;
int bval;
int diff;
aval=a[i];
bval=b[i];
if(aval>='a'&&aval<='z') {
aval-='a'-'A';
}
if(bval>='a'&&bval<='z'){
bval-='a'-'A';
}
diff=aval-bval;
if(diff){
return diff;
}
}
return 0;
}
int is_jpeg(const unsigned char *buf, size_t length){
return length>=11&&memcmp(buf,"\xFF\xD8\xFF\xE0",4)==0
&&(buf[4]<<8|buf[5])>=16&&memcmp(buf+6,"JFIF",5)==0;
}
int is_png(const unsigned char *buf, size_t length){
return length>=8&&memcmp(buf,"\x89PNG\x0D\x0A\x1A\x0A",8)==0;
}
int is_gif(const unsigned char *buf, size_t length){
return length>=6
&&(memcmp(buf,"GIF87a",6)==0||memcmp(buf,"GIF89a",6)==0);
}
#define READ_U32_BE(buf) \
(((buf)[0]<<24)|((buf)[1]<<16)|((buf)[2]<<8)|((buf)[3]&0xff))
/*Tries to extract the width, height, bits per pixel, and palette size of a
PNG.
On failure, simply leaves its outputs unmodified.*/
void extract_png_params(const unsigned char *data, size_t data_length,
ogg_uint32_t *width, ogg_uint32_t *height,
ogg_uint32_t *depth, ogg_uint32_t *colors,
int *has_palette){
if(is_png(data,data_length)){
size_t offs;
offs=8;
while(data_length-offs>=12){
ogg_uint32_t chunk_len;
chunk_len=READ_U32_BE(data+offs);
if(chunk_len>data_length-(offs+12))break;
else if(chunk_len==13&&memcmp(data+offs+4,"IHDR",4)==0){
int color_type;
*width=READ_U32_BE(data+offs+8);
*height=READ_U32_BE(data+offs+12);
color_type=data[offs+17];
if(color_type==3){
*depth=24;
*has_palette=1;
}
else{
int sample_depth;
sample_depth=data[offs+16];
if(color_type==0)*depth=sample_depth;
else if(color_type==2)*depth=sample_depth*3;
else if(color_type==4)*depth=sample_depth*2;
else if(color_type==6)*depth=sample_depth*4;
*colors=0;
*has_palette=0;
break;
}
}
else if(*has_palette>0&&memcmp(data+offs+4,"PLTE",4)==0){
*colors=chunk_len/3;
break;
}
offs+=12+chunk_len;
}
}
}
/*Tries to extract the width, height, bits per pixel, and palette size of a
GIF.
On failure, simply leaves its outputs unmodified.*/
void extract_gif_params(const unsigned char *data, size_t data_length,
ogg_uint32_t *width, ogg_uint32_t *height,
ogg_uint32_t *depth, ogg_uint32_t *colors,
int *has_palette){
if(is_gif(data,data_length)&&data_length>=14){
*width=data[6]|data[7]<<8;
*height=data[8]|data[9]<<8;
/*libFLAC hard-codes the depth to 24.*/
*depth=24;
*colors=1<<((data[10]&7)+1);
*has_palette=1;
}
}
/*Tries to extract the width, height, bits per pixel, and palette size of a
JPEG.
On failure, simply leaves its outputs unmodified.*/
void extract_jpeg_params(const unsigned char *data, size_t data_length,
ogg_uint32_t *width, ogg_uint32_t *height,
ogg_uint32_t *depth, ogg_uint32_t *colors,
int *has_palette){
if(is_jpeg(data,data_length)){
size_t offs;
offs=2;
for(;;){
size_t segment_len;
int marker;
while(offs<data_length&&data[offs]!=0xFF)offs++;
while(offs<data_length&&data[offs]==0xFF)offs++;
marker=data[offs];
offs++;
/*If we hit EOI* (end of image), or another SOI* (start of image),
or SOS (start of scan), then stop now.*/
if(offs>=data_length||(marker>=0xD8&&marker<=0xDA))break;
/*RST* (restart markers): skip (no segment length).*/
else if(marker>=0xD0&&marker<=0xD7)continue;
/*Read the length of the marker segment.*/
if(data_length-offs<2)break;
segment_len=data[offs]<<8|data[offs+1];
if(segment_len<2||data_length-offs<segment_len)break;
if(marker==0xC0||(marker>0xC0&&marker<0xD0&&(marker&3)!=0)){
/*Found a SOFn (start of frame) marker segment:*/
if(segment_len>=8){
*height=data[offs+3]<<8|data[offs+4];
*width=data[offs+5]<<8|data[offs+6];
*depth=data[offs+2]*data[offs+7];
*colors=0;
*has_palette=0;
}
break;
}
/*Other markers: skip the whole marker segment.*/
offs+=segment_len;
}
}
}
#define IMAX(a,b) ((a) > (b) ? (a) : (b))
/*Parse a picture SPECIFICATION as given on the command-line.
spec: The specification.
error_message: Returns an error message on error.
seen_file_icons: Bit flags used to track if any pictures of type 1 or type 2
have already been added, to ensure only one is allowed.
Return: A Base64-encoded string suitable for use in a METADATA_BLOCK_PICTURE
tag.*/
char *parse_picture_specification(const char *spec,
const char **error_message,
int *seen_file_icons){
FILE *picture_file;
unsigned long picture_type;
unsigned long width;
unsigned long height;
unsigned long depth;
unsigned long colors;
const char *mime_type;
const char *mime_type_end;
const char *description;
const char *description_end;
const char *filename;
unsigned char *buf;
char *out;
size_t cbuf;
size_t nbuf;
size_t data_offset;
size_t data_length;
size_t b64_length;
int is_url;
/*If a filename has a '|' in it, there's no way we can distinguish it from a
full specification just from the spec string.
Instead, try to open the file.
If it exists, the user probably meant the file.*/
picture_type=3;
width=height=depth=colors=0;
mime_type=mime_type_end=description=description_end=filename=spec;
is_url=0;
picture_file=fopen(filename,"rb");
if(picture_file==NULL&&strchr(spec,'|')){
const char *p;
char *q;
/*We don't have a plain file, and there is a pipe character: assume it's
the full form of the specification.*/
picture_type=strtoul(spec,&q,10);
if(*q!='|'||picture_type>20){
*error_message="invalid picture type";
return NULL;
}
if(picture_type>=1&&picture_type<=2&&(*seen_file_icons&picture_type)){
*error_message=picture_type==1?
"only one picture of type 1 (32x32 icon) allowed":
"only one picture of type 2 (icon) allowed";
return NULL;
}
/*An empty field implies a default of 'Cover (front)'.*/
if(spec==q)picture_type=3;
mime_type=q+1;
mime_type_end=mime_type+strcspn(mime_type,"|");
if(*mime_type_end!='|'){
*error_message="invalid picture specification: not enough fields";
return NULL;
}
/*The mime type must be composed of ASCII printable characters 0x20-0x7E.*/
for(p=mime_type;p<mime_type_end;p++)if(*p<0x20||*p>0x7E){
*error_message="invalid characters in mime type";
return NULL;
}
is_url=mime_type_end-mime_type==3
&&strncmp("-->",mime_type,mime_type_end-mime_type)==0;
description=mime_type_end+1;
description_end=description+strcspn(description,"|");
if(*description_end!='|'){
*error_message="invalid picture specification: not enough fields";
return NULL;
}
p=description_end+1;
if(*p!='|'){
width=strtoul(p,&q,10);
if(*q!='x'){
*error_message=
"invalid picture specification: can't parse resolution/color field";
return NULL;
}
p=q+1;
height=strtoul(p,&q,10);
if(*q!='x'){
*error_message=
"invalid picture specification: can't parse resolution/color field";
return NULL;
}
p=q+1;
depth=strtoul(p,&q,10);
if(*q=='/'){
p=q+1;
colors=strtoul(p,&q,10);
}
if(*q!='|'){
*error_message=
"invalid picture specification: can't parse resolution/color field";
return NULL;
}
p=q;
}
filename=p+1;
if(!is_url)picture_file=fopen(filename,"rb");
}
/*Buffer size: 8 static 4-byte fields plus 2 dynamic fields, plus the
file/URL data.
We reserve at least 10 bytes for the mime type, in case we still need to
extract it from the file.*/
data_offset=32+(description_end-description)+IMAX(mime_type_end-mime_type,10);
buf=NULL;
if(is_url){
/*Easy case: just stick the URL at the end.
We don't do anything to verify it's a valid URL.*/
data_length=strlen(filename);
cbuf=nbuf=data_offset+data_length;
buf=(unsigned char *)malloc(cbuf);
memcpy(buf+data_offset,filename,data_length);
}
else{
ogg_uint32_t file_width;
ogg_uint32_t file_height;
ogg_uint32_t file_depth;
ogg_uint32_t file_colors;
int has_palette;
/*Complicated case: we have a real file.
Read it in, attempt to parse the mime type and image dimensions if
necessary, and validate what the user passed in.*/
if(picture_file==NULL){
*error_message="error opening picture file";
return NULL;
}
nbuf=data_offset;
/*Add a reasonable starting image file size.*/
cbuf=data_offset+65536;
for(;;){
unsigned char *new_buf;
size_t nread;
new_buf=realloc(buf,cbuf);
if(new_buf==NULL){
fclose(picture_file);
free(buf);
*error_message="insufficient memory";
return NULL;
}
buf=new_buf;
nread=fread(buf+nbuf,1,cbuf-nbuf,picture_file);
nbuf+=nread;
if(nbuf<cbuf){
int error;
error=ferror(picture_file);
fclose(picture_file);
if(error){
free(buf);
*error_message="error reading picture file";
return NULL;
}
break;
}
if(cbuf==0xFFFFFFFF){
fclose(picture_file);
free(buf);
*error_message="file too large";
return NULL;
}
else if(cbuf>0x7FFFFFFFU)cbuf=0xFFFFFFFFU;
else cbuf=cbuf<<1|1;
}
data_length=nbuf-data_offset;
/*If there was no mimetype, try to extract it from the file data.*/
if(mime_type_end==mime_type){
if(is_jpeg(buf+data_offset,data_length)){
mime_type="image/jpeg";
mime_type_end=mime_type+10;
}
else if(is_png(buf+data_offset,data_length)){
mime_type="image/png";
mime_type_end=mime_type+9;
}
else if(is_gif(buf+data_offset,data_length)){
mime_type="image/gif";
mime_type_end=mime_type+9;
}
else{
free(buf);
*error_message="unable to guess MIME type from file, "
"must set it explicitly";
return NULL;
}
}
/*Try to extract the image dimensions/color information from the file.*/
file_width=file_height=file_depth=file_colors=0;
has_palette=-1;
if(mime_type_end-mime_type==9
&&oi_strncasecmp("image/png",mime_type,mime_type_end-mime_type)==0){
extract_png_params(buf+data_offset,data_length,
&file_width,&file_height,&file_depth,&file_colors,&has_palette);
}
else if(mime_type_end-mime_type==9
&&oi_strncasecmp("image/gif",mime_type,mime_type_end-mime_type)==0){
extract_gif_params(buf+data_offset,data_length,
&file_width,&file_height,&file_depth,&file_colors,&has_palette);
}
else if(mime_type_end-mime_type==10
&&oi_strncasecmp("image/jpeg",mime_type,mime_type_end-mime_type)==0){
extract_jpeg_params(buf+data_offset,data_length,
&file_width,&file_height,&file_depth,&file_colors,&has_palette);
}
if(!width)width=file_width;
if(!height)height=file_height;
if(!depth)depth=file_depth;
if(!colors)colors=file_colors;
if((file_width&&width!=file_width)
||(file_height&&height!=file_height)
||(file_depth&&depth!=file_depth)
/*We use has_palette to ensure we also reject non-0 user color counts for
images we've positively identified as non-paletted.*/
||(has_palette>=0&&colors!=file_colors)){
free(buf);
*error_message="invalid picture specification: "
"resolution/color field does not match file";
return NULL;
}
}
/*These fields MUST be set correctly OR all set to zero.
So if any of them (except colors, for which 0 is a valid value) are still
zero, clear the rest to zero.*/
if(width==0||height==0||depth==0)width=height=depth=colors=0;
if(picture_type==1&&(width!=32||height!=32
||mime_type_end-mime_type!=9
||oi_strncasecmp("image/png",mime_type,mime_type_end-mime_type)!=0)){
free(buf);
*error_message="pictures of type 1 MUST be 32x32 PNGs";
return NULL;
}
/*Build the METADATA_BLOCK_PICTURE buffer.
We do this backwards from data_offset, because we didn't necessarily know
how big the mime type string was before we read the data in.*/
data_offset-=4;
WRITE_U32_BE(buf+data_offset,(unsigned long)data_length);
data_offset-=4;
WRITE_U32_BE(buf+data_offset,colors);
data_offset-=4;
WRITE_U32_BE(buf+data_offset,depth);
data_offset-=4;
WRITE_U32_BE(buf+data_offset,height);
data_offset-=4;
WRITE_U32_BE(buf+data_offset,width);
data_offset-=description_end-description;
memcpy(buf+data_offset,description,description_end-description);
data_offset-=4;
WRITE_U32_BE(buf+data_offset,(unsigned long)(description_end-description));
data_offset-=mime_type_end-mime_type;
memcpy(buf+data_offset,mime_type,mime_type_end-mime_type);
data_offset-=4;
WRITE_U32_BE(buf+data_offset,(unsigned long)(mime_type_end-mime_type));
data_offset-=4;
WRITE_U32_BE(buf+data_offset,picture_type);
data_length=nbuf-data_offset;
b64_length=BASE64_LENGTH(data_length);
out=(char *)malloc(b64_length+1);
if(out!=NULL){
base64_encode(out,(char *)buf+data_offset,data_length);
if(picture_type>=1&&picture_type<=2)*seen_file_icons|=picture_type;
}
free(buf);
return out;
}
@@ -0,0 +1,50 @@
#ifndef __PICTURE_H
#define __PICTURE_H
#include <ogg/ogg.h>
typedef enum{
PIC_FORMAT_JPEG,
PIC_FORMAT_PNG,
PIC_FORMAT_GIF
}picture_format;
#define BASE64_LENGTH(len) (((len)+2)/3*4)
/*Utility function for base64 encoding METADATA_BLOCK_PICTURE tags.
Stores BASE64_LENGTH(len)+1 bytes in dst (including a terminating NUL).*/
void base64_encode(char *dst, const char *src, int len);
int oi_strncasecmp(const char *a, const char *b, int n);
int is_jpeg(const unsigned char *buf, size_t length);
int is_png(const unsigned char *buf, size_t length);
int is_gif(const unsigned char *buf, size_t length);
void extract_png_params(const unsigned char *data, size_t data_length,
ogg_uint32_t *width, ogg_uint32_t *height,
ogg_uint32_t *depth, ogg_uint32_t *colors,
int *has_palette);
void extract_gif_params(const unsigned char *data, size_t data_length,
ogg_uint32_t *width, ogg_uint32_t *height,
ogg_uint32_t *depth, ogg_uint32_t *colors,
int *has_palette);
void extract_jpeg_params(const unsigned char *data, size_t data_length,
ogg_uint32_t *width, ogg_uint32_t *height,
ogg_uint32_t *depth, ogg_uint32_t *colors,
int *has_palette);
char *parse_picture_specification(const char *spec,
const char **error_message,
int *seen_file_icons);
#define WRITE_U32_BE(buf, val) \
do{ \
(buf)[0]=(unsigned char)((val)>>24); \
(buf)[1]=(unsigned char)((val)>>16); \
(buf)[2]=(unsigned char)((val)>>8); \
(buf)[3]=(unsigned char)(val); \
} \
while(0);
#endif /* __PICTURE_H */
@@ -0,0 +1,125 @@
/* Copyright (C) 2002 Jean-Marc Valin
File: wav_io.c
Routines to handle wav (RIFF) headers
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifdef HAVE_CONFIG_H
# include "config.h"
#endif
#include <stdio.h>
#include <string.h>
#include "wav_io.h"
#include "opus_header.h"
/* Adjust the stream->channel mapping to ensure the proper output order for
WAV files. */
void adjust_wav_mapping(int mapping_family, int channels, unsigned char *stream_map)
{
unsigned char new_stream_map[8];
int i;
/* If we aren't using one of the defined semantic channel maps, or we have
more channels than we know what to do with, use a default 1-1 mapping. */
if(mapping_family != 1 || channels > 8)
return;
for(i = 0; i < channels; i++)
{
new_stream_map[wav_permute_matrix[channels-1][i]] = stream_map[i];
}
memcpy(stream_map, new_stream_map, channels*sizeof(*stream_map));
}
static size_t fwrite_le32(opus_int32 i32, FILE *file)
{
unsigned char buf[4];
buf[0]=(unsigned char)(i32&0xFF);
buf[1]=(unsigned char)(i32>>8&0xFF);
buf[2]=(unsigned char)(i32>>16&0xFF);
buf[3]=(unsigned char)(i32>>24&0xFF);
return fwrite(buf,4,1,file);
}
static size_t fwrite_le16(int i16, FILE *file)
{
unsigned char buf[2];
buf[0]=(unsigned char)(i16&0xFF);
buf[1]=(unsigned char)(i16>>8&0xFF);
return fwrite(buf,2,1,file);
}
int write_wav_header(FILE *file, int rate, int mapping_family, int channels)
{
int ret;
int extensible;
/* Multichannel files require a WAVEFORMATEXTENSIBLE header to declare the
proper channel meanings. */
extensible = mapping_family == 1 && 3 <= channels && channels <= 8;
ret = fprintf (file, "RIFF") >= 0;
ret &= fwrite_le32 (0x7fffffff, file);
ret &= fprintf (file, "WAVEfmt ") >= 0;
ret &= fwrite_le32 (extensible ? 40 : 16, file);
ret &= fwrite_le16 (extensible ? 0xfffe : 1, file);
ret &= fwrite_le16 (channels, file);
ret &= fwrite_le32 (rate, file);
ret &= fwrite_le32 (2*channels*rate, file);
ret &= fwrite_le16 (2*channels, file);
ret &= fwrite_le16 (16, file);
if(extensible)
{
static const unsigned char ksdataformat_subtype_pcm[16]=
{
0x01, 0x00, 0x00, 0x00,
0x00, 0x00,
0x10, 0x00,
0x80, 0x00,
0x00, 0xaa, 0x00, 0x38, 0x9b, 0x71
};
static const int wav_channel_masks[8] =
{
1, /* 1.0 mono */
1|2, /* 2.0 stereo */
1|2|4, /* 3.0 channel ('wide') stereo */
1|2|16|32, /* 4.0 discrete quadrophonic */
1|2|4|16|32, /* 5.0 */
1|2|4|8|16|32, /* 5.1 */
1|2|4|8|256|512|1024, /* 6.1 */
1|2|4|8|16|32|512|1024, /* 7.1 */
};
ret &= fwrite_le16 (22, file);
ret &= fwrite_le16 (16, file);
ret &= fwrite_le32 (wav_channel_masks[channels-1], file);
ret &= fwrite (ksdataformat_subtype_pcm, 16, 1, file);
}
ret &= fprintf (file, "data") >= 0;
ret &= fwrite_le32 (0x7fffffff, file);
return !ret ? -1 : extensible ? 40 : 16;
}
@@ -0,0 +1,62 @@
/* Copyright (C) 2002 Jean-Marc Valin
File: wav_io.h
Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions
are met:
- Redistributions of source code must retain the above copyright
notice, this list of conditions and the following disclaimer.
- Redistributions in binary form must reproduce the above copyright
notice, this list of conditions and the following disclaimer in the
documentation and/or other materials provided with the distribution.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR
A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR
CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL,
EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
#ifndef WAV_IO_H
#define WAV_IO_H
#include <stdio.h>
#include <opus/opus_types.h>
#if !defined(__LITTLE_ENDIAN__) && ( defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) )
#define le_short(s) ((short) ((unsigned short) (s) << 8) | ((unsigned short) (s) >> 8))
#define be_short(s) ((short) (s))
#else
#define le_short(s) ((short) (s))
#define be_short(s) ((short) ((unsigned short) (s) << 8) | ((unsigned short) (s) >> 8))
#endif
/** Convert little endian */
static inline opus_int32 le_int(opus_int32 i)
{
#if !defined(__LITTLE_ENDIAN__) && ( defined(WORDS_BIGENDIAN) || defined(__BIG_ENDIAN__) )
opus_uint32 ui, ret;
ui = i;
ret = ui>>24;
ret |= (ui>>8)&0x0000ff00;
ret |= (ui<<8)&0x00ff0000;
ret |= (ui<<24);
return ret;
#else
return i;
#endif
}
void adjust_wav_mapping(int mapping_family, int channels, unsigned char *stream_map);
int write_wav_header(FILE *file, int rate, int mapping_family, int channels);
#endif