0
|
1 /*
|
|
2 * Ogg bitstream support
|
|
3 * Mark Hills <mark@pogo.org.uk>
|
|
4 *
|
|
5 * Uses libogg, but requires libvorbisenc to construct correct headers
|
|
6 * when containing Vorbis stream -- currently the only format supported
|
|
7 */
|
|
8
|
|
9 #include <stdio.h>
|
|
10 #include <time.h>
|
|
11
|
|
12 #include <ogg/ogg.h>
|
|
13 #include <vorbis/vorbisenc.h>
|
|
14
|
|
15 #include "avformat.h"
|
|
16 #include "oggvorbis.h"
|
|
17
|
|
18 #define DECODER_BUFFER_SIZE 4096
|
|
19
|
|
20
|
|
21 typedef struct OggContext {
|
|
22 /* output */
|
|
23 ogg_stream_state os ;
|
|
24 int header_handled ;
|
|
25 ogg_int64_t base_packet_no ;
|
|
26 ogg_int64_t base_granule_pos ;
|
|
27
|
|
28 /* input */
|
|
29 ogg_sync_state oy ;
|
|
30 } OggContext ;
|
|
31
|
|
32
|
|
33 static int ogg_write_header(AVFormatContext *avfcontext) {
|
|
34 OggContext *context ;
|
|
35 AVCodecContext *avccontext ;
|
|
36 vorbis_info vi ;
|
|
37 vorbis_dsp_state vd ;
|
|
38 vorbis_comment vc ;
|
|
39 vorbis_block vb ;
|
|
40 ogg_packet header, header_comm, header_code ;
|
|
41 int n ;
|
|
42
|
|
43 if(!(context = malloc(sizeof(OggContext))))
|
|
44 return -1 ;
|
|
45 avfcontext->priv_data = context ;
|
|
46
|
|
47 srand(time(NULL));
|
|
48 ogg_stream_init(&context->os, rand());
|
|
49
|
|
50 for(n = 0 ; n < avfcontext->nb_streams ; n++) {
|
|
51 avccontext = &avfcontext->streams[n]->codec ;
|
|
52
|
|
53 /* begin vorbis specific code */
|
|
54
|
|
55 vorbis_info_init(&vi) ;
|
|
56
|
|
57 /* code copied from libavcodec/oggvorbis.c */
|
|
58
|
|
59 if(oggvorbis_init_encoder(&vi, avccontext) < 0) {
|
|
60 fprintf(stderr, "ogg_write_header: init_encoder failed") ;
|
|
61 return -1 ;
|
|
62 }
|
|
63
|
|
64 vorbis_analysis_init(&vd, &vi) ;
|
|
65 vorbis_block_init(&vd, &vb) ;
|
|
66
|
|
67 vorbis_comment_init(&vc) ;
|
|
68 vorbis_comment_add_tag(&vc, "encoder", "ffmpeg") ;
|
|
69 if(*avfcontext->title)
|
|
70 vorbis_comment_add_tag(&vc, "title", avfcontext->title) ;
|
|
71
|
|
72 vorbis_analysis_headerout(&vd, &vc, &header,
|
|
73 &header_comm, &header_code) ;
|
|
74 ogg_stream_packetin(&context->os, &header) ;
|
|
75 ogg_stream_packetin(&context->os, &header_comm) ;
|
|
76 ogg_stream_packetin(&context->os, &header_code) ;
|
|
77
|
|
78 vorbis_comment_clear(&vc) ;
|
|
79
|
|
80 /* end of vorbis specific code */
|
|
81
|
|
82 context->header_handled = 0 ;
|
|
83 context->base_packet_no = 0 ;
|
|
84 }
|
|
85
|
|
86 return 0 ;
|
|
87 }
|
|
88
|
|
89
|
|
90 static int ogg_write_packet(AVFormatContext *avfcontext,
|
|
91 int stream_index,
|
|
92 unsigned char *buf, int size, int force_pts)
|
|
93 {
|
|
94 OggContext *context = avfcontext->priv_data ;
|
|
95 ogg_packet *op ;
|
|
96 ogg_page og ;
|
|
97 int l = 0 ;
|
|
98
|
|
99 /* flush header packets so audio starts on a new page */
|
|
100
|
|
101 if(!context->header_handled) {
|
|
102 while(ogg_stream_flush(&context->os, &og)) {
|
|
103 put_buffer(&avfcontext->pb, og.header, og.header_len) ;
|
|
104 put_buffer(&avfcontext->pb, og.body, og.body_len) ;
|
|
105 put_flush_packet(&avfcontext->pb);
|
|
106 }
|
|
107 context->header_handled = 1 ;
|
|
108 }
|
|
109
|
|
110 while(l < size) {
|
|
111 op = (ogg_packet*)(buf + l) ;
|
|
112 op->packet = buf + l + sizeof(ogg_packet) ; /* fix data pointer */
|
|
113
|
|
114 if(!context->base_packet_no) { /* this is the first packet */
|
|
115 context->base_packet_no = op->packetno ;
|
|
116 context->base_granule_pos = op->granulepos ;
|
|
117 }
|
|
118
|
|
119 /* correct the fields in the packet -- essential for streaming */
|
|
120
|
|
121 op->packetno -= context->base_packet_no ;
|
|
122 op->granulepos -= context->base_granule_pos ;
|
|
123
|
|
124 ogg_stream_packetin(&context->os, op) ;
|
|
125 l += sizeof(ogg_packet) + op->bytes ;
|
|
126
|
|
127 while(ogg_stream_pageout(&context->os, &og)) {
|
|
128 put_buffer(&avfcontext->pb, og.header, og.header_len) ;
|
|
129 put_buffer(&avfcontext->pb, og.body, og.body_len) ;
|
|
130 put_flush_packet(&avfcontext->pb);
|
|
131 }
|
|
132 }
|
|
133
|
|
134 return 0;
|
|
135 }
|
|
136
|
|
137
|
|
138 static int ogg_write_trailer(AVFormatContext *avfcontext) {
|
|
139 OggContext *context = avfcontext->priv_data ;
|
|
140 ogg_page og ;
|
|
141
|
|
142 while(ogg_stream_flush(&context->os, &og)) {
|
|
143 put_buffer(&avfcontext->pb, og.header, og.header_len) ;
|
|
144 put_buffer(&avfcontext->pb, og.body, og.body_len) ;
|
|
145 put_flush_packet(&avfcontext->pb);
|
|
146 }
|
|
147
|
|
148 ogg_stream_clear(&context->os) ;
|
|
149 return 0 ;
|
|
150 }
|
|
151
|
|
152
|
|
153 static AVOutputFormat ogg_oformat = {
|
|
154 "ogg",
|
|
155 "Ogg Vorbis",
|
|
156 "audio/x-vorbis",
|
|
157 "ogg",
|
|
158 sizeof(OggContext),
|
|
159 CODEC_ID_VORBIS,
|
|
160 0,
|
|
161 ogg_write_header,
|
|
162 ogg_write_packet,
|
|
163 ogg_write_trailer,
|
|
164 } ;
|
|
165
|
|
166
|
|
167 static int next_packet(AVFormatContext *avfcontext, ogg_packet *op) {
|
|
168 OggContext *context = avfcontext->priv_data ;
|
|
169 ogg_page og ;
|
|
170 char *buf ;
|
|
171
|
|
172 while(ogg_stream_packetout(&context->os, op) != 1) {
|
|
173
|
|
174 /* while no pages are available, read in more data to the sync */
|
|
175 while(ogg_sync_pageout(&context->oy, &og) != 1) {
|
|
176 buf = ogg_sync_buffer(&context->oy, DECODER_BUFFER_SIZE) ;
|
|
177 if(get_buffer(&avfcontext->pb, buf, DECODER_BUFFER_SIZE) <= 0)
|
|
178 return 1 ;
|
|
179 ogg_sync_wrote(&context->oy, DECODER_BUFFER_SIZE) ;
|
|
180 }
|
|
181
|
|
182 /* got a page. Feed it into the stream and get the packet */
|
|
183 if(ogg_stream_pagein(&context->os, &og) != 0)
|
|
184 return 1 ;
|
|
185 }
|
|
186
|
|
187 return 0 ;
|
|
188 }
|
|
189
|
|
190
|
|
191 static int ogg_read_header(AVFormatContext *avfcontext, AVFormatParameters *ap)
|
|
192 {
|
|
193 OggContext *context ;
|
|
194 char *buf ;
|
|
195 ogg_page og ;
|
|
196 AVStream *ast ;
|
|
197
|
|
198 if(!(context = malloc(sizeof(OggContext)))) {
|
|
199 perror("malloc") ;
|
|
200 return -1 ;
|
|
201 }
|
|
202 avfcontext->priv_data = context ;
|
|
203
|
|
204 ogg_sync_init(&context->oy) ;
|
|
205 buf = ogg_sync_buffer(&context->oy, DECODER_BUFFER_SIZE) ;
|
|
206
|
|
207 if(get_buffer(&avfcontext->pb, buf, DECODER_BUFFER_SIZE) <= 0)
|
|
208 return -EIO ;
|
|
209
|
|
210 ogg_sync_wrote(&context->oy, DECODER_BUFFER_SIZE) ;
|
|
211 ogg_sync_pageout(&context->oy, &og) ;
|
|
212 ogg_stream_init(&context->os, ogg_page_serialno(&og)) ;
|
|
213 ogg_stream_pagein(&context->os, &og) ;
|
|
214
|
|
215 /* currently only one vorbis stream supported */
|
|
216
|
|
217 ast = av_new_stream(avfcontext, 0) ;
|
|
218 if(!ast)
|
|
219 return AVERROR_NOMEM ;
|
|
220
|
|
221 ast->codec.codec_type = CODEC_TYPE_AUDIO ;
|
|
222 ast->codec.codec_id = CODEC_ID_VORBIS ;
|
|
223
|
|
224 return 0 ;
|
|
225 }
|
|
226
|
|
227
|
|
228 static int ogg_read_packet(AVFormatContext *avfcontext, AVPacket *pkt) {
|
|
229 ogg_packet op ;
|
|
230
|
|
231 if(next_packet(avfcontext, &op))
|
|
232 return -EIO ;
|
|
233 if(av_new_packet(pkt, sizeof(ogg_packet) + op.bytes) < 0)
|
|
234 return -EIO ;
|
|
235 pkt->stream_index = 0 ;
|
|
236 memcpy(pkt->data, &op, sizeof(ogg_packet)) ;
|
|
237 memcpy(pkt->data + sizeof(ogg_packet), op.packet, op.bytes) ;
|
|
238
|
|
239 return sizeof(ogg_packet) + op.bytes ;
|
|
240 }
|
|
241
|
|
242
|
|
243 static int ogg_read_close(AVFormatContext *avfcontext) {
|
|
244 OggContext *context = avfcontext->priv_data ;
|
|
245
|
|
246 ogg_stream_clear(&context->os) ;
|
|
247 ogg_sync_clear(&context->oy) ;
|
|
248
|
|
249 return 0 ;
|
|
250 }
|
|
251
|
|
252
|
|
253 static AVInputFormat ogg_iformat = {
|
|
254 "ogg",
|
|
255 "Ogg Vorbis",
|
|
256 sizeof(OggContext),
|
|
257 NULL,
|
|
258 ogg_read_header,
|
|
259 ogg_read_packet,
|
|
260 ogg_read_close,
|
|
261 .extensions = "ogg",
|
|
262 } ;
|
|
263
|
|
264
|
|
265 int ogg_init(void) {
|
|
266 av_register_output_format(&ogg_oformat) ;
|
|
267 av_register_input_format(&ogg_iformat);
|
|
268 return 0 ;
|
|
269 }
|