blob: ca646170064d3d8d614182d4c911cc3c3c334a01 [file] [log] [blame]
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00001/* $Id$ */
2/*
Benny Prijonoa771a512007-02-19 01:13:53 +00003 * Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
Benny Prijonob3cdb2b2006-10-19 15:49:47 +00004 *
5 * This program is free software; you can redistribute it and/or modify
6 * it under the terms of the GNU General Public License as published by
7 * the Free Software Foundation; either version 2 of the License, or
8 * (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
14 *
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, write to the Free Software
17 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
18 */
19
20/*
21 * Contributed by:
22 * Toni < buldozer at aufbix dot org >
23 */
Benny Prijonob94a9f72007-04-30 11:05:23 +000024#include "mp3_port.h"
Benny Prijonob3cdb2b2006-10-19 15:49:47 +000025#include <pjmedia/errno.h>
26#include <pj/assert.h>
27#include <pj/file_access.h>
28#include <pj/file_io.h>
29#include <pj/log.h>
30#include <pj/pool.h>
31#include <pj/string.h>
32#include <pj/unicode.h>
33
34
35/* Include BladeDLL declarations */
36#include "BladeMP3EncDLL.h"
37
38
39#define THIS_FILE "mp3_writer.c"
40#define SIGNATURE PJMEDIA_PORT_SIGNATURE('F', 'W', 'M', '3')
41#define BYTES_PER_SAMPLE 2
42
43static struct BladeDLL
44{
45 void *hModule;
46 int refCount;
47 BEINITSTREAM beInitStream;
48 BEENCODECHUNK beEncodeChunk;
49 BEDEINITSTREAM beDeinitStream;
50 BECLOSESTREAM beCloseStream;
51 BEVERSION beVersion;
52 BEWRITEVBRHEADER beWriteVBRHeader;
53 BEWRITEINFOTAG beWriteInfoTag;
54} BladeDLL;
55
56
57struct mp3_file_port
58{
59 pjmedia_port base;
60 pj_size_t total;
61 pj_oshandle_t fd;
62 pj_size_t cb_size;
63 pj_status_t (*cb)(pjmedia_port*, void*);
64
65 unsigned silence_duration;
66
67 pj_str_t mp3_filename;
68 pjmedia_mp3_encoder_option mp3_option;
69 unsigned mp3_samples_per_frame;
70 pj_int16_t *mp3_sample_buf;
71 unsigned mp3_sample_pos;
72 HBE_STREAM mp3_stream;
73 unsigned char *mp3_buf;
74};
75
76
77static pj_status_t file_put_frame(pjmedia_port *this_port,
78 const pjmedia_frame *frame);
79static pj_status_t file_get_frame(pjmedia_port *this_port,
80 pjmedia_frame *frame);
81static pj_status_t file_on_destroy(pjmedia_port *this_port);
82
83
84#if defined(PJ_WIN32) || defined(_WIN32) || defined(WIN32)
85
86#include <windows.h>
87#define DLL_NAME PJ_T("LAME_ENC.DLL")
88
89/*
90 * Load BladeEncoder DLL.
91 */
92static pj_status_t init_blade_dll(void)
93{
94 if (BladeDLL.refCount == 0) {
95 #define GET_PROC(type, name) \
96 BladeDLL.name = (type)GetProcAddress(BladeDLL.hModule, PJ_T(#name)); \
97 if (BladeDLL.name == NULL) { \
98 PJ_LOG(1,(THIS_FILE, "Unable to find %s in %s", #name, DLL_NAME)); \
99 return PJ_RETURN_OS_ERROR(GetLastError()); \
100 }
101
102 BE_VERSION beVersion;
103 BladeDLL.hModule = (void*)LoadLibrary(DLL_NAME);
104 if (BladeDLL.hModule == NULL) {
105 pj_status_t status = PJ_RETURN_OS_ERROR(GetLastError());
106 char errmsg[PJ_ERR_MSG_SIZE];
107
108 pj_strerror(status, errmsg, sizeof(errmsg));
109 PJ_LOG(1,(THIS_FILE, "Unable to load %s: %s", DLL_NAME, errmsg));
110 return status;
111 }
112
113 GET_PROC(BEINITSTREAM, beInitStream);
114 GET_PROC(BEENCODECHUNK, beEncodeChunk);
115 GET_PROC(BEDEINITSTREAM, beDeinitStream);
116 GET_PROC(BECLOSESTREAM, beCloseStream);
117 GET_PROC(BEVERSION, beVersion);
118 GET_PROC(BEWRITEVBRHEADER, beWriteVBRHeader);
119 GET_PROC(BEWRITEINFOTAG, beWriteInfoTag);
120
121 #undef GET_PROC
122
123 BladeDLL.beVersion(&beVersion);
124 PJ_LOG(4,(THIS_FILE, "%s encoder v%d.%d loaded (%s)", DLL_NAME,
125 beVersion.byMajorVersion, beVersion.byMinorVersion,
126 beVersion.zHomepage));
127 }
128 ++BladeDLL.refCount;
129 return PJ_SUCCESS;
130}
131
132/*
133 * Decrement the reference counter of the DLL.
134 */
135static void deinit_blade_dll()
136{
137 --BladeDLL.refCount;
138 if (BladeDLL.refCount == 0 && BladeDLL.hModule) {
139 FreeLibrary(BladeDLL.hModule);
140 BladeDLL.hModule = NULL;
141 PJ_LOG(4,(THIS_FILE, "%s unloaded", DLL_NAME));
142 }
143}
144
145#else
146
147static pj_status_t init_blade_dll(void)
148{
149 PJ_LOG(1,(THIS_FILE, "Error: MP3 writer port only works on Windows for now"));
150 return PJ_ENOTSUP;
151}
152
153static void deinit_blade_dll()
154{
155}
156#endif
157
158
159
160/*
161 * Initialize MP3 encoder.
162 */
163static pj_status_t init_mp3_encoder(struct mp3_file_port *fport,
164 pj_pool_t *pool)
165{
166 BE_CONFIG LConfig;
167 unsigned long InSamples;
168 unsigned long OutBuffSize;
169 long MP3Err;
170
171 /*
172 * Initialize encoder configuration.
173 */
174 pj_bzero(&LConfig, sizeof(BE_CONFIG));
175 LConfig.dwConfig = BE_CONFIG_LAME;
176 LConfig.format.LHV1.dwStructVersion = 1;
177 LConfig.format.LHV1.dwStructSize = sizeof(BE_CONFIG);
178 LConfig.format.LHV1.dwSampleRate = fport->base.info.clock_rate;
179 LConfig.format.LHV1.dwReSampleRate = 0;
180
181 if (fport->base.info.channel_count==1)
182 LConfig.format.LHV1.nMode = BE_MP3_MODE_MONO;
183 else if (fport->base.info.channel_count==2)
184 LConfig.format.LHV1.nMode = BE_MP3_MODE_STEREO;
185 else
186 return PJMEDIA_ENCCHANNEL;
187
188 LConfig.format.LHV1.dwBitrate = fport->mp3_option.bit_rate / 1000;
189 LConfig.format.LHV1.nPreset = LQP_NOPRESET;
190 LConfig.format.LHV1.bCopyright = 0;
191 LConfig.format.LHV1.bCRC = 1;
192 LConfig.format.LHV1.bOriginal = 1;
193 LConfig.format.LHV1.bPrivate = 0;
194
195 if (!fport->mp3_option.vbr) {
196 LConfig.format.LHV1.nVbrMethod = VBR_METHOD_NONE;
197 LConfig.format.LHV1.bWriteVBRHeader = 0;
198 LConfig.format.LHV1.bEnableVBR = 0;
199 } else {
200 LConfig.format.LHV1.nVbrMethod = VBR_METHOD_DEFAULT;
201 LConfig.format.LHV1.bWriteVBRHeader = 1;
Benny Prijono8f310522006-10-20 11:08:49 +0000202 LConfig.format.LHV1.dwVbrAbr_bps = fport->mp3_option.bit_rate;
Benny Prijono9a44fe22006-10-21 09:23:37 +0000203 LConfig.format.LHV1.nVBRQuality = (pj_uint16_t)
204 fport->mp3_option.quality;
Benny Prijonob3cdb2b2006-10-19 15:49:47 +0000205 LConfig.format.LHV1.bEnableVBR = 1;
206 }
207
Benny Prijono9a44fe22006-10-21 09:23:37 +0000208 LConfig.format.LHV1.nQuality = (pj_uint16_t)
209 (((0-fport->mp3_option.quality-1)<<8) |
210 fport->mp3_option.quality);
Benny Prijonob3cdb2b2006-10-19 15:49:47 +0000211
212 /*
213 * Init MP3 stream.
214 */
215 InSamples = 0;
216 MP3Err = BladeDLL.beInitStream(&LConfig, &InSamples, &OutBuffSize,
217 &fport->mp3_stream);
218 if (MP3Err != BE_ERR_SUCCESSFUL)
219 return PJMEDIA_ERROR;
220
221 /*
222 * Allocate sample buffer.
223 */
224 fport->mp3_samples_per_frame = (unsigned)InSamples;
225 fport->mp3_sample_buf = pj_pool_alloc(pool, fport->mp3_samples_per_frame * 2);
226 if (!fport->mp3_sample_buf)
227 return PJ_ENOMEM;
228
229 /*
230 * Allocate encoded MP3 buffer.
231 */
232 fport->mp3_buf = pj_pool_alloc(pool, (pj_size_t)OutBuffSize);
233 if (fport->mp3_buf == NULL)
234 return PJ_ENOMEM;
235
236
237 return PJ_SUCCESS;
238}
239
240
241/*
242 * Create MP3 file writer port.
243 */
244PJ_DEF(pj_status_t)
245pjmedia_mp3_writer_port_create( pj_pool_t *pool,
246 const char *filename,
247 unsigned sampling_rate,
248 unsigned channel_count,
249 unsigned samples_per_frame,
250 unsigned bits_per_sample,
251 const pjmedia_mp3_encoder_option *param_option,
252 pjmedia_port **p_port )
253{
254 struct mp3_file_port *fport;
255 pj_status_t status;
256
257 status = init_blade_dll();
258 if (status != PJ_SUCCESS)
259 return status;
260
261 /* Check arguments. */
262 PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL);
263
264 /* Only supports 16bits per sample for now. */
265 PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
266
267 /* Create file port instance. */
268 fport = pj_pool_zalloc(pool, sizeof(struct mp3_file_port));
269 PJ_ASSERT_RETURN(fport != NULL, PJ_ENOMEM);
270
271 /* Initialize port info. */
272 pj_strdup2_with_null(pool, &fport->mp3_filename, filename);
273 pjmedia_port_info_init(&fport->base.info, &fport->mp3_filename, SIGNATURE,
274 sampling_rate, channel_count, bits_per_sample,
275 samples_per_frame);
276
277 fport->base.get_frame = &file_get_frame;
278 fport->base.put_frame = &file_put_frame;
279 fport->base.on_destroy = &file_on_destroy;
280
281
282 /* Open file in write and read mode.
283 * We need the read mode because we'll modify the WAVE header once
284 * the recording has completed.
285 */
286 status = pj_file_open(pool, filename, PJ_O_WRONLY, &fport->fd);
287 if (status != PJ_SUCCESS) {
288 deinit_blade_dll();
289 return status;
290 }
291
292 /* Copy and initialize option with default settings */
293 if (param_option) {
294 pj_memcpy(&fport->mp3_option, param_option,
295 sizeof(pjmedia_mp3_encoder_option));
296 } else {
297 pj_bzero(&fport->mp3_option, sizeof(pjmedia_mp3_encoder_option));
298 fport->mp3_option.vbr = PJ_TRUE;
299 }
300
Benny Prijono8f310522006-10-20 11:08:49 +0000301 /* Calculate bitrate if it's not specified, only if it's not VBR. */
302 if (fport->mp3_option.bit_rate == 0 && !fport->mp3_option.vbr)
Benny Prijonob3cdb2b2006-10-19 15:49:47 +0000303 fport->mp3_option.bit_rate = sampling_rate * channel_count;
304
305 /* Set default quality if it's not specified */
306 if (fport->mp3_option.quality == 0)
307 fport->mp3_option.quality = 2;
308
309 /* Init mp3 encoder */
310 status = init_mp3_encoder(fport, pool);
311 if (status != PJ_SUCCESS) {
312 pj_file_close(fport->fd);
313 deinit_blade_dll();
314 return status;
315 }
316
317 /* Done. */
318 *p_port = &fport->base;
319
320 PJ_LOG(4,(THIS_FILE,
321 "MP3 file writer '%.*s' created: samp.rate=%dKHz, "
322 "bitrate=%dkbps%s, quality=%d",
323 (int)fport->base.info.name.slen,
324 fport->base.info.name.ptr,
325 fport->base.info.clock_rate/1000,
326 fport->mp3_option.bit_rate/1000,
327 (fport->mp3_option.vbr ? " (VBR)" : ""),
328 fport->mp3_option.quality));
329
330 return PJ_SUCCESS;
331}
332
333
334
335/*
336 * Register callback.
337 */
338PJ_DEF(pj_status_t)
339pjmedia_mp3_writer_port_set_cb( pjmedia_port *port,
340 pj_size_t pos,
341 void *user_data,
342 pj_status_t (*cb)(pjmedia_port *port,
343 void *usr_data))
344{
345 struct mp3_file_port *fport;
346
347 /* Sanity check */
348 PJ_ASSERT_RETURN(port && cb, PJ_EINVAL);
349
350 /* Check that this is really a writer port */
351 PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVALIDOP);
352
353 fport = (struct mp3_file_port*) port;
354
355 fport->cb_size = pos;
356 fport->base.port_data.pdata = user_data;
357 fport->cb = cb;
358
359 return PJ_SUCCESS;
360
361}
362
363
364/*
365 * Put a frame into the buffer. When the buffer is full, flush the buffer
366 * to the file.
367 */
368static pj_status_t file_put_frame(pjmedia_port *this_port,
369 const pjmedia_frame *frame)
370{
371 struct mp3_file_port *fport = (struct mp3_file_port *)this_port;
372 unsigned long MP3Err;
373 pj_ssize_t bytes;
374 pj_status_t status;
375 unsigned long WriteSize;
376
377 /* Record silence if input is no-frame */
378 if (frame->type == PJMEDIA_FRAME_TYPE_NONE || frame->size == 0) {
379 unsigned samples_left = fport->base.info.samples_per_frame;
380 unsigned samples_copied = 0;
381
382 /* Only want to record at most 1 second of silence */
383 if (fport->silence_duration >= fport->base.info.clock_rate)
384 return PJ_SUCCESS;
385
386 while (samples_left) {
387 unsigned samples_needed = fport->mp3_samples_per_frame -
388 fport->mp3_sample_pos;
389 if (samples_needed > samples_left)
390 samples_needed = samples_left;
391
392 pjmedia_zero_samples(fport->mp3_sample_buf + fport->mp3_sample_pos,
393 samples_needed);
394 fport->mp3_sample_pos += samples_needed;
395 samples_left -= samples_needed;
396 samples_copied += samples_needed;
397
398 /* Encode if we have full frame */
399 if (fport->mp3_sample_pos == fport->mp3_samples_per_frame) {
400
401 /* Clear position */
402 fport->mp3_sample_pos = 0;
403
404 /* Encode ! */
405 MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream,
406 fport->mp3_samples_per_frame,
407 fport->mp3_sample_buf,
408 fport->mp3_buf,
409 &WriteSize);
410 if (MP3Err != BE_ERR_SUCCESSFUL)
411 return PJMEDIA_ERROR;
412
413 /* Write the chunk */
414 bytes = WriteSize;
415 status = pj_file_write(fport->fd, fport->mp3_buf, &bytes);
416 if (status != PJ_SUCCESS)
417 return status;
418
419 /* Increment total written. */
420 fport->total += bytes;
421 }
422 }
423
424 fport->silence_duration += fport->base.info.samples_per_frame;
425
426 }
427 /* If encoder is expecting different sample size, then we need to
428 * buffer the samples.
429 */
430 else if (fport->mp3_samples_per_frame !=
431 fport->base.info.samples_per_frame)
432 {
433 unsigned samples_left = frame->size / 2;
434 unsigned samples_copied = 0;
435 const pj_int16_t *src_samples = frame->buf;
436
437 fport->silence_duration = 0;
438
439 while (samples_left) {
440 unsigned samples_needed = fport->mp3_samples_per_frame -
441 fport->mp3_sample_pos;
442 if (samples_needed > samples_left)
443 samples_needed = samples_left;
444
445 pjmedia_copy_samples(fport->mp3_sample_buf + fport->mp3_sample_pos,
446 src_samples + samples_copied,
447 samples_needed);
448 fport->mp3_sample_pos += samples_needed;
449 samples_left -= samples_needed;
450 samples_copied += samples_needed;
451
452 /* Encode if we have full frame */
453 if (fport->mp3_sample_pos == fport->mp3_samples_per_frame) {
454
455 /* Clear position */
456 fport->mp3_sample_pos = 0;
457
458 /* Encode ! */
459 MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream,
460 fport->mp3_samples_per_frame,
461 fport->mp3_sample_buf,
462 fport->mp3_buf,
463 &WriteSize);
464 if (MP3Err != BE_ERR_SUCCESSFUL)
465 return PJMEDIA_ERROR;
466
467 /* Write the chunk */
468 bytes = WriteSize;
469 status = pj_file_write(fport->fd, fport->mp3_buf, &bytes);
470 if (status != PJ_SUCCESS)
471 return status;
472
473 /* Increment total written. */
474 fport->total += bytes;
475 }
476 }
477
478 } else {
479
480 fport->silence_duration = 0;
481
482 /* Encode ! */
483 MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream,
484 fport->mp3_samples_per_frame,
485 frame->buf,
486 fport->mp3_buf,
487 &WriteSize);
488 if (MP3Err != BE_ERR_SUCCESSFUL)
489 return PJMEDIA_ERROR;
490
491 /* Write the chunk */
492 bytes = WriteSize;
493 status = pj_file_write(fport->fd, fport->mp3_buf, &bytes);
494 if (status != PJ_SUCCESS)
495 return status;
496
497 /* Increment total written. */
498 fport->total += bytes;
499 }
500
501 /* Increment total written, and check if we need to call callback */
502
503 if (fport->cb && fport->total >= fport->cb_size) {
504 pj_status_t (*cb)(pjmedia_port*, void*);
505 pj_status_t status;
506
507 cb = fport->cb;
508 fport->cb = NULL;
509
510 status = (*cb)(this_port, this_port->port_data.pdata);
511 return status;
512 }
513
514 return PJ_SUCCESS;
515}
516
517/*
518 * Get frame, basicy is a no-op operation.
519 */
520static pj_status_t file_get_frame(pjmedia_port *this_port,
521 pjmedia_frame *frame)
522{
523 PJ_UNUSED_ARG(this_port);
524 PJ_UNUSED_ARG(frame);
525 return PJ_EINVALIDOP;
526}
527
528
529/*
530 * Close the port, modify file header with updated file length.
531 */
532static pj_status_t file_on_destroy(pjmedia_port *this_port)
533{
534 struct mp3_file_port *fport = (struct mp3_file_port*)this_port;
535 pj_status_t status;
536 unsigned long WriteSize;
537 unsigned long MP3Err;
538
539
540 /* Close encoder */
541 MP3Err = BladeDLL.beDeinitStream(fport->mp3_stream, fport->mp3_buf,
542 &WriteSize);
543 if (MP3Err == BE_ERR_SUCCESSFUL) {
544 pj_ssize_t bytes = WriteSize;
545 status = pj_file_write(fport->fd, fport->mp3_buf, &bytes);
546 }
547
548 /* Close file */
549 status = pj_file_close(fport->fd);
550
551 /* Write additional VBR header */
552 if (fport->mp3_option.vbr) {
553 MP3Err = BladeDLL.beWriteVBRHeader(fport->mp3_filename.ptr);
554 }
555
556
557 /* Decrement DLL reference counter */
558 deinit_blade_dll();
559
560 /* Done. */
561 return PJ_SUCCESS;
562}
563