summaryrefslogtreecommitdiff
path: root/encoders/vorbis_encoder.cpp
blob: 44493459b6a0d39d011bcedc48d2fb9d5e174e42 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
#include "vorbis_encoder.h"

#include <stdexcept>
#include <cstring>

VorbisEncoder::VorbisEncoder(RawAudioSource::p source_) : source(source_) {
	vorbis_info_init(&vi);
	vorbis_encode_init_vbr(&vi, 2, 44100, .4);
	vorbis_analysis_init(&dsp, &vi);
	vorbis_block_init(&dsp, &vb);

	vorbis_comment vc;
	vorbis_comment_init(&vc);
	// add tags and stuff here
	ogg_packet op, op_comm, op_code;
	vorbis_analysis_headerout(&dsp, &vc, &op, &op_comm, &op_code);

	ogg_stream_init(&os, 0);
	ogg_stream_packetin(&os, &op);
	ogg_stream_packetin(&os, &op_comm);
	ogg_stream_packetin(&os, &op_code);

	headers_written = false;
}

VorbisEncoder::~VorbisEncoder() {
	vorbis_info_clear(&vi);
	vorbis_dsp_clear(&dsp);
	vorbis_block_clear(&vb);
	ogg_stream_clear(&os);
}

/** Write ogg stream pages to the buffer.
 *  \return Number of bytes written.
 */
std::size_t VorbisEncoder::write_pages(char *buf, std::size_t buf_size) {
	std::size_t written = 0;
	while(ogg_stream_pageout(&os, &og)) {
		std::size_t total_len = (std::size_t)(og.header_len + og.body_len);
		// TODO: Handle this somehow
		if(written + total_len > buf_size) {
			throw std::runtime_error("page too large");
		}
		std::memcpy(buf, og.header, og.header_len);
		std::memcpy(buf+og.header_len, og.body, og.body_len);
		written = og.header_len + og.body_len;
	}
	return written;
}

/** Read from source and write pages.
 *  Encodes a number of samples read from \c source,
 *  then calls \c write_pages to write completed pages.
 *  \return Number of bytes written by \c write_pages.
 */
std::size_t VorbisEncoder::encode(char *buf, std::size_t buf_size) {
	const int samples_n = 1024;

	// samples_n samples, 2 channels, 2 byte per sample
	char src_data[samples_n*2*2];
	int16_t *src_data_16 = (int16_t*)src_data;

	std::streamsize src_read = source->read(src_data, samples_n*2*2);
	if(src_read % 4) {
		throw std::runtime_error("invalid buffer size");
	}

	if(src_read == 0) {
		vorbis_analysis_wrote(&dsp, 0);
	} else {
		int samples_read = src_read / 4;
		float **buffer = vorbis_analysis_buffer(&dsp, samples_read);
		int sample;
		for(sample = 0; sample < samples_read; sample++) {
			for(int c = 0; c < 2; c++) {
				int i = sample*2+c;
				if(i*2 >= src_read) break;
				buffer[c][sample] = src_data_16[sample*2+c]/32768.;
			}
		}
		vorbis_analysis_wrote(&dsp, sample);
	}

	while(vorbis_analysis_blockout(&dsp, &vb) == 1) {
		vorbis_analysis(&vb, NULL);
		vorbis_bitrate_addblock(&vb);
		ogg_packet op;
		while(vorbis_bitrate_flushpacket(&dsp, &op) == 1) {
			ogg_stream_packetin(&os, &op);
		}
	}

	return write_pages(buf, buf_size);
}

std::size_t VorbisEncoder::read(char* buf, std::size_t buf_size) {
	if(!headers_written) {
		headers_written = true;
		if(!ogg_stream_flush(&os, &og)) {
			throw std::runtime_error("couldn't flush ogg stream header");
		}
		std::memcpy(buf, og.header, og.header_len);
		std::memcpy(buf+og.header_len, og.body, og.body_len);
		return og.header_len + og.body_len;
	}

	std::size_t written = 0;

	// run until we have a page or reached end of stream
	while(written == 0 && !ogg_page_eos(&og)) {
		written = encode(buf, buf_size);
	}

	return written;
}

std::string VorbisEncoder::get_mime_type() {
	return "application/ogg";
}