1
/* $Id: mp3_writer.c 1233 2007-04-30 11:05:23Z bennylp $ */
3
* Copyright (C) 2003-2007 Benny Prijono <benny@prijono.org>
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.
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.
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
22
* Toni < buldozer at aufbix dot org >
25
#include <pjmedia/errno.h>
26
#include <pj/assert.h>
27
#include <pj/file_access.h>
28
#include <pj/file_io.h>
31
#include <pj/string.h>
32
#include <pj/unicode.h>
35
/* Include BladeDLL declarations */
36
#include "BladeMP3EncDLL.h"
39
#define THIS_FILE "mp3_writer.c"
40
#define SIGNATURE PJMEDIA_PORT_SIGNATURE('F', 'W', 'M', '3')
41
#define BYTES_PER_SAMPLE 2
43
static struct BladeDLL
47
BEINITSTREAM beInitStream;
48
BEENCODECHUNK beEncodeChunk;
49
BEDEINITSTREAM beDeinitStream;
50
BECLOSESTREAM beCloseStream;
52
BEWRITEVBRHEADER beWriteVBRHeader;
53
BEWRITEINFOTAG beWriteInfoTag;
63
pj_status_t (*cb)(pjmedia_port*, void*);
65
unsigned silence_duration;
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;
77
static pj_status_t file_put_frame(pjmedia_port *this_port,
78
const pjmedia_frame *frame);
79
static pj_status_t file_get_frame(pjmedia_port *this_port,
80
pjmedia_frame *frame);
81
static pj_status_t file_on_destroy(pjmedia_port *this_port);
84
#if defined(PJ_WIN32) || defined(_WIN32) || defined(WIN32)
87
#define DLL_NAME PJ_T("LAME_ENC.DLL")
90
* Load BladeEncoder DLL.
92
static pj_status_t init_blade_dll(void)
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()); \
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];
108
pj_strerror(status, errmsg, sizeof(errmsg));
109
PJ_LOG(1,(THIS_FILE, "Unable to load %s: %s", DLL_NAME, errmsg));
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);
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));
133
* Decrement the reference counter of the DLL.
135
static void deinit_blade_dll()
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));
147
static pj_status_t init_blade_dll(void)
149
PJ_LOG(1,(THIS_FILE, "Error: MP3 writer port only works on Windows for now"));
153
static void deinit_blade_dll()
161
* Initialize MP3 encoder.
163
static pj_status_t init_mp3_encoder(struct mp3_file_port *fport,
167
unsigned long InSamples;
168
unsigned long OutBuffSize;
172
* Initialize encoder configuration.
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;
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;
186
return PJMEDIA_ENCCHANNEL;
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;
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;
200
LConfig.format.LHV1.nVbrMethod = VBR_METHOD_DEFAULT;
201
LConfig.format.LHV1.bWriteVBRHeader = 1;
202
LConfig.format.LHV1.dwVbrAbr_bps = fport->mp3_option.bit_rate;
203
LConfig.format.LHV1.nVBRQuality = (pj_uint16_t)
204
fport->mp3_option.quality;
205
LConfig.format.LHV1.bEnableVBR = 1;
208
LConfig.format.LHV1.nQuality = (pj_uint16_t)
209
(((0-fport->mp3_option.quality-1)<<8) |
210
fport->mp3_option.quality);
216
MP3Err = BladeDLL.beInitStream(&LConfig, &InSamples, &OutBuffSize,
218
if (MP3Err != BE_ERR_SUCCESSFUL)
219
return PJMEDIA_ERROR;
222
* Allocate sample buffer.
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)
230
* Allocate encoded MP3 buffer.
232
fport->mp3_buf = pj_pool_alloc(pool, (pj_size_t)OutBuffSize);
233
if (fport->mp3_buf == NULL)
242
* Create MP3 file writer port.
245
pjmedia_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 )
254
struct mp3_file_port *fport;
257
status = init_blade_dll();
258
if (status != PJ_SUCCESS)
261
/* Check arguments. */
262
PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL);
264
/* Only supports 16bits per sample for now. */
265
PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);
267
/* Create file port instance. */
268
fport = pj_pool_zalloc(pool, sizeof(struct mp3_file_port));
269
PJ_ASSERT_RETURN(fport != NULL, PJ_ENOMEM);
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,
277
fport->base.get_frame = &file_get_frame;
278
fport->base.put_frame = &file_put_frame;
279
fport->base.on_destroy = &file_on_destroy;
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.
286
status = pj_file_open(pool, filename, PJ_O_WRONLY, &fport->fd);
287
if (status != PJ_SUCCESS) {
292
/* Copy and initialize option with default settings */
294
pj_memcpy(&fport->mp3_option, param_option,
295
sizeof(pjmedia_mp3_encoder_option));
297
pj_bzero(&fport->mp3_option, sizeof(pjmedia_mp3_encoder_option));
298
fport->mp3_option.vbr = PJ_TRUE;
301
/* 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)
303
fport->mp3_option.bit_rate = sampling_rate * channel_count;
305
/* Set default quality if it's not specified */
306
if (fport->mp3_option.quality == 0)
307
fport->mp3_option.quality = 2;
309
/* Init mp3 encoder */
310
status = init_mp3_encoder(fport, pool);
311
if (status != PJ_SUCCESS) {
312
pj_file_close(fport->fd);
318
*p_port = &fport->base;
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));
339
pjmedia_mp3_writer_port_set_cb( pjmedia_port *port,
342
pj_status_t (*cb)(pjmedia_port *port,
345
struct mp3_file_port *fport;
348
PJ_ASSERT_RETURN(port && cb, PJ_EINVAL);
350
/* Check that this is really a writer port */
351
PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVALIDOP);
353
fport = (struct mp3_file_port*) port;
355
fport->cb_size = pos;
356
fport->base.port_data.pdata = user_data;
365
* Put a frame into the buffer. When the buffer is full, flush the buffer
368
static pj_status_t file_put_frame(pjmedia_port *this_port,
369
const pjmedia_frame *frame)
371
struct mp3_file_port *fport = (struct mp3_file_port *)this_port;
372
unsigned long MP3Err;
375
unsigned long WriteSize;
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;
382
/* Only want to record at most 1 second of silence */
383
if (fport->silence_duration >= fport->base.info.clock_rate)
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;
392
pjmedia_zero_samples(fport->mp3_sample_buf + fport->mp3_sample_pos,
394
fport->mp3_sample_pos += samples_needed;
395
samples_left -= samples_needed;
396
samples_copied += samples_needed;
398
/* Encode if we have full frame */
399
if (fport->mp3_sample_pos == fport->mp3_samples_per_frame) {
402
fport->mp3_sample_pos = 0;
405
MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream,
406
fport->mp3_samples_per_frame,
407
fport->mp3_sample_buf,
410
if (MP3Err != BE_ERR_SUCCESSFUL)
411
return PJMEDIA_ERROR;
413
/* Write the chunk */
415
status = pj_file_write(fport->fd, fport->mp3_buf, &bytes);
416
if (status != PJ_SUCCESS)
419
/* Increment total written. */
420
fport->total += bytes;
424
fport->silence_duration += fport->base.info.samples_per_frame;
427
/* If encoder is expecting different sample size, then we need to
428
* buffer the samples.
430
else if (fport->mp3_samples_per_frame !=
431
fport->base.info.samples_per_frame)
433
unsigned samples_left = frame->size / 2;
434
unsigned samples_copied = 0;
435
const pj_int16_t *src_samples = frame->buf;
437
fport->silence_duration = 0;
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;
445
pjmedia_copy_samples(fport->mp3_sample_buf + fport->mp3_sample_pos,
446
src_samples + samples_copied,
448
fport->mp3_sample_pos += samples_needed;
449
samples_left -= samples_needed;
450
samples_copied += samples_needed;
452
/* Encode if we have full frame */
453
if (fport->mp3_sample_pos == fport->mp3_samples_per_frame) {
456
fport->mp3_sample_pos = 0;
459
MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream,
460
fport->mp3_samples_per_frame,
461
fport->mp3_sample_buf,
464
if (MP3Err != BE_ERR_SUCCESSFUL)
465
return PJMEDIA_ERROR;
467
/* Write the chunk */
469
status = pj_file_write(fport->fd, fport->mp3_buf, &bytes);
470
if (status != PJ_SUCCESS)
473
/* Increment total written. */
474
fport->total += bytes;
480
fport->silence_duration = 0;
483
MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream,
484
fport->mp3_samples_per_frame,
488
if (MP3Err != BE_ERR_SUCCESSFUL)
489
return PJMEDIA_ERROR;
491
/* Write the chunk */
493
status = pj_file_write(fport->fd, fport->mp3_buf, &bytes);
494
if (status != PJ_SUCCESS)
497
/* Increment total written. */
498
fport->total += bytes;
501
/* Increment total written, and check if we need to call callback */
503
if (fport->cb && fport->total >= fport->cb_size) {
504
pj_status_t (*cb)(pjmedia_port*, void*);
510
status = (*cb)(this_port, this_port->port_data.pdata);
518
* Get frame, basicy is a no-op operation.
520
static pj_status_t file_get_frame(pjmedia_port *this_port,
521
pjmedia_frame *frame)
523
PJ_UNUSED_ARG(this_port);
524
PJ_UNUSED_ARG(frame);
525
return PJ_EINVALIDOP;
530
* Close the port, modify file header with updated file length.
532
static pj_status_t file_on_destroy(pjmedia_port *this_port)
534
struct mp3_file_port *fport = (struct mp3_file_port*)this_port;
536
unsigned long WriteSize;
537
unsigned long MP3Err;
541
MP3Err = BladeDLL.beDeinitStream(fport->mp3_stream, fport->mp3_buf,
543
if (MP3Err == BE_ERR_SUCCESSFUL) {
544
pj_ssize_t bytes = WriteSize;
545
status = pj_file_write(fport->fd, fport->mp3_buf, &bytes);
549
status = pj_file_close(fport->fd);
551
/* Write additional VBR header */
552
if (fport->mp3_option.vbr) {
553
MP3Err = BladeDLL.beWriteVBRHeader(fport->mp3_filename.ptr);
557
/* Decrement DLL reference counter */