MP3 Writer fix and update

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index]

 



 Hi,


When I updated to latest SVN revision of PJSIP I noticed that my MP3 recordings stopped working. The reason is usage of unicode strings in the GetProcAddress calls when resolving LAME's functions. As I also had another issue with recording to Samba drives over VPN connections (big latency while recording frames 'kills' audio in calls completely), I also upgraded the MP3 recorder with option to record in separate thread (added a define for WRITER_USES_THREAD) (same idea could be applied to WAV writer easilly). I'm attaching the updated file for all that could use it.


 Greets, Toni
/* $Id: mp3_writer.c 4483 2013-04-19 09:52:02Z ming $ */
/* 
 * Copyright (C) 2003-2007 Benny Prijono <benny@xxxxxxxxxxx>
 *
 * This program is free software; you can redistribute it and/or modify
 * it under the terms of the GNU General Public License as published by
 * the Free Software Foundation; either version 2 of the License, or
 * (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA 
 */

/*
 * Contributed by:
 *  Toni < buldozer at aufbix dot org >
 */
#include "mp3_port.h"
#include <pjmedia/errno.h>
#include <pj/assert.h>
#include <pj/file_access.h>
#include <pj/file_io.h>
#include <pj/log.h>
#include <pj/pool.h>
#include <pj/string.h>
#include <pj/unicode.h>


/* Include BladeDLL declarations */
#include "BladeMP3EncDLL.h"


#define THIS_FILE	    "mp3_writer.c"
#define SIGNATURE	    PJMEDIA_SIG_CLASS_PORT_AUD('M','W')
#define BYTES_PER_SAMPLE    2

static struct BladeDLL
{
	void		*hModule;
	int			 refCount;
	BEINITSTREAM	 beInitStream;
	BEENCODECHUNK	 beEncodeChunk;
	BEDEINITSTREAM	 beDeinitStream;
	BECLOSESTREAM	 beCloseStream;
	BEVERSION		 beVersion;
	BEWRITEVBRHEADER	 beWriteVBRHeader;
	BEWRITEINFOTAG	 beWriteInfoTag;
} BladeDLL;


struct mp3_file_port
{
	pjmedia_port    base;
	pj_size_t	    total;
	pj_oshandle_t   fd;
	pj_size_t	    cb_size;
	pj_status_t	   (*cb)(pjmedia_port*, void*);

	unsigned	    silence_duration;

	pj_str_t					mp3_filename;
	pjmedia_mp3_encoder_option	mp3_option;
	unsigned					mp3_samples_per_frame;
	pj_int16_t					*mp3_sample_buf;
	unsigned					mp3_sample_pos;
	HBE_STREAM					mp3_stream;
	unsigned char				*mp3_buf;

#ifdef WRITER_USES_THREAD
	struct write_buff {
		PJ_DECL_LIST_MEMBER(struct write_buff);
		void*		buffer;
		pj_ssize_t	size;
	};
	pj_thread_t			*writer_thread;
	struct write_buff	writer_buffers;
	pj_mutex_t			*writer_buffers_mutex;
	pj_bool_t			writer_finish;
	pj_bool_t			writer_finished;
#endif
};


static pj_status_t file_put_frame(pjmedia_port *this_port, 
				  const pjmedia_frame *frame);
static pj_status_t file_get_frame(pjmedia_port *this_port, 
				  pjmedia_frame *frame);
static pj_status_t file_on_destroy(pjmedia_port *this_port);

static pj_status_t internal_file_write(pj_oshandle_t fd, const void *data, pj_ssize_t *size, pjmedia_port *this_port);

#ifdef WRITER_USES_THREAD
static	void*	threaded_writer(pjmedia_port *this_port);
#endif

#if defined(PJ_WIN32) || defined(_WIN32) || defined(WIN32)

#include <windows.h>
#define DLL_NAME    PJ_T("LAME_ENC.DLL")

/*
 * Load BladeEncoder DLL.
 */
static pj_status_t init_blade_dll(void)
{
	if (BladeDLL.refCount == 0) {
	#define GET_PROC(type, name)  \
		BladeDLL.name = (type)GetProcAddress(BladeDLL.hModule, #name); \
		if (BladeDLL.name == NULL) { \
		PJ_LOG(1,(THIS_FILE, "Unable to find %s in %s", #name, DLL_NAME)); \
		FreeLibrary(BladeDLL.hModule); \
		return PJ_RETURN_OS_ERROR(GetLastError()); \
		}

	BE_VERSION beVersion;
	BladeDLL.hModule = (void*)LoadLibrary(DLL_NAME);
	if (BladeDLL.hModule==NULL) {
		pj_status_t status = PJ_RETURN_OS_ERROR(GetLastError());
		char errmsg[PJ_ERR_MSG_SIZE];

		pj_strerror(status, errmsg, sizeof(errmsg));
		PJ_LOG(1,(THIS_FILE, "Unable to load %s: %s", DLL_NAME, errmsg));
		return status;
	}

	GET_PROC(BEINITSTREAM, beInitStream);
	GET_PROC(BEENCODECHUNK, beEncodeChunk);
	GET_PROC(BEDEINITSTREAM, beDeinitStream);
	GET_PROC(BECLOSESTREAM, beCloseStream);
	GET_PROC(BEVERSION, beVersion);
	GET_PROC(BEWRITEVBRHEADER, beWriteVBRHeader);
	GET_PROC(BEWRITEINFOTAG, beWriteInfoTag);

	#undef GET_PROC

	BladeDLL.beVersion(&beVersion);
	PJ_LOG(4,(THIS_FILE, "%s encoder v%d.%d loaded (%s)", DLL_NAME,
		  beVersion.byMajorVersion, beVersion.byMinorVersion,
		  beVersion.zHomepage));
	}
	++BladeDLL.refCount;
	return PJ_SUCCESS;
}

/*
 * Decrement the reference counter of the DLL.
 */
static void deinit_blade_dll()
{
	--BladeDLL.refCount;
	if (BladeDLL.refCount == 0 && BladeDLL.hModule) {
	FreeLibrary(BladeDLL.hModule);
	BladeDLL.hModule = NULL;
	PJ_LOG(4,(THIS_FILE, "%s unloaded", DLL_NAME));
	}
}

#else

static pj_status_t init_blade_dll(void)
{
	PJ_LOG(1,(THIS_FILE, "Error: MP3 writer port only works on Windows for now"));
	return PJ_ENOTSUP;
}

static void deinit_blade_dll()
{
}
#endif



/*
 * Initialize MP3 encoder.
 */
static pj_status_t init_mp3_encoder(struct mp3_file_port *fport,
					pj_pool_t *pool)
{
	BE_CONFIG LConfig;
	unsigned  long InSamples;
	unsigned  long OutBuffSize;
	long MP3Err;

	/*
	 * Initialize encoder configuration.
	 */
	pj_bzero(&LConfig, sizeof(BE_CONFIG));
	LConfig.dwConfig = BE_CONFIG_LAME;
	LConfig.format.LHV1.dwStructVersion = 1;
	LConfig.format.LHV1.dwStructSize = sizeof(BE_CONFIG);
	LConfig.format.LHV1.dwSampleRate = PJMEDIA_PIA_SRATE(&fport->base.info);
	LConfig.format.LHV1.dwReSampleRate = 0;

	if (PJMEDIA_PIA_CCNT(&fport->base.info)==1)
	LConfig.format.LHV1.nMode = BE_MP3_MODE_MONO;
	else if (PJMEDIA_PIA_CCNT(&fport->base.info)==2)
	LConfig.format.LHV1.nMode = BE_MP3_MODE_STEREO;
	else
	return PJMEDIA_ENCCHANNEL;

	LConfig.format.LHV1.dwBitrate = fport->mp3_option.bit_rate / 1000;
	LConfig.format.LHV1.nPreset = LQP_NOPRESET;
	LConfig.format.LHV1.bCopyright = 0;
	LConfig.format.LHV1.bCRC = 1;
	LConfig.format.LHV1.bOriginal = 1;
	LConfig.format.LHV1.bPrivate = 0;

	if (!fport->mp3_option.vbr) {
	LConfig.format.LHV1.nVbrMethod = VBR_METHOD_NONE;
	LConfig.format.LHV1.bWriteVBRHeader = 0;
	LConfig.format.LHV1.bEnableVBR = 0;
	} else {
	LConfig.format.LHV1.nVbrMethod = VBR_METHOD_DEFAULT;
	LConfig.format.LHV1.bWriteVBRHeader = 1;
	LConfig.format.LHV1.dwVbrAbr_bps = fport->mp3_option.bit_rate;
	LConfig.format.LHV1.nVBRQuality =  (pj_uint16_t)
					   fport->mp3_option.quality;
	LConfig.format.LHV1.bEnableVBR = 1;
	}

	LConfig.format.LHV1.nQuality = (pj_uint16_t) 
				   (((0-fport->mp3_option.quality-1)<<8) | 
					fport->mp3_option.quality);

	/*
	 * Init MP3 stream.
	 */
	InSamples = 0;
	MP3Err = BladeDLL.beInitStream(&LConfig, &InSamples, &OutBuffSize,
				   &fport->mp3_stream);
	if (MP3Err != BE_ERR_SUCCESSFUL) 
	return PJMEDIA_ERROR;

	/*
	 * Allocate sample buffer.
	 */
	fport->mp3_samples_per_frame = (unsigned)InSamples;
	fport->mp3_sample_buf = pj_pool_alloc(pool, fport->mp3_samples_per_frame * 2);
	if (!fport->mp3_sample_buf)
	return PJ_ENOMEM;

	/*
	 * Allocate encoded MP3 buffer.
	 */
	fport->mp3_buf = pj_pool_alloc(pool, (pj_size_t)OutBuffSize);
	if (fport->mp3_buf == NULL)
	return PJ_ENOMEM;

	
	return PJ_SUCCESS;
}


/*
 * Create MP3 file writer port.
 */
PJ_DEF(pj_status_t) 
pjmedia_mp3_writer_port_create( pj_pool_t *pool,
				const char *filename,
				unsigned sampling_rate,
				unsigned channel_count,
				unsigned samples_per_frame,
				unsigned bits_per_sample,
				const pjmedia_mp3_encoder_option *param_option,
				pjmedia_port **p_port )
{
	struct mp3_file_port *fport;
	pj_status_t status;

	status = init_blade_dll();
	if (status != PJ_SUCCESS)
	return status;

	/* Check arguments. */
	PJ_ASSERT_RETURN(pool && filename && p_port, PJ_EINVAL);

	/* Only supports 16bits per sample for now. */
	PJ_ASSERT_RETURN(bits_per_sample == 16, PJ_EINVAL);

	/* Create file port instance. */
	fport = pj_pool_zalloc(pool, sizeof(struct mp3_file_port));
	PJ_ASSERT_RETURN(fport != NULL, PJ_ENOMEM);

	/* Initialize port info. */
	pj_strdup2_with_null(pool, &fport->mp3_filename, filename);
	pjmedia_port_info_init(&fport->base.info, &fport->mp3_filename, SIGNATURE,
			   sampling_rate, channel_count, bits_per_sample,
			   samples_per_frame);

	fport->base.get_frame = &file_get_frame;
	fport->base.put_frame = &file_put_frame;
	fport->base.on_destroy = &file_on_destroy;


	/* Open file in write and read mode.
	 * We need the read mode because we'll modify the WAVE header once
	 * the recording has completed.
	 */
	status = pj_file_open(pool, filename, PJ_O_WRONLY, &fport->fd);
	if (status != PJ_SUCCESS) {
	deinit_blade_dll();
	return status;
	}

	/* Copy and initialize option with default settings */
	if (param_option) {
	pj_memcpy(&fport->mp3_option, param_option, 
		   sizeof(pjmedia_mp3_encoder_option));
	} else {
	pj_bzero(&fport->mp3_option, sizeof(pjmedia_mp3_encoder_option));
	fport->mp3_option.vbr = PJ_TRUE;
	}

	/* Calculate bitrate if it's not specified, only if it's not VBR. */
	if (fport->mp3_option.bit_rate == 0 && !fport->mp3_option.vbr) 
	fport->mp3_option.bit_rate = sampling_rate * channel_count;

	/* Set default quality if it's not specified */
	if (fport->mp3_option.quality == 0) 
	fport->mp3_option.quality = 2;

	/* Init mp3 encoder */
	status = init_mp3_encoder(fport, pool);
	if (status != PJ_SUCCESS) {
	pj_file_close(fport->fd);
	deinit_blade_dll();
	return status;
	}

#ifdef WRITER_USES_THREAD
	status=pj_thread_create(pool, "WriteThread", (pj_thread_proc*)&threaded_writer, fport, PJ_THREAD_DEFAULT_STACK_SIZE, 0, &fport->writer_thread);
	if (status!=PJ_SUCCESS) {
		fport->writer_finished=PJ_TRUE;
		pj_file_close(fport->fd);
		deinit_blade_dll();
		return status;
	}
	status=pj_mutex_create(pool, "WriteMutex", PJ_MUTEX_DEFAULT, &fport->writer_buffers_mutex);
	if (status!=PJ_SUCCESS) {
		fport->writer_finished=PJ_TRUE;
		pj_thread_destroy(fport->writer_thread);
		pj_file_close(fport->fd);
		deinit_blade_dll();
		return status;
	}
	pj_list_init(&fport->writer_buffers);
#endif // WRITER_USES_THREAD


	/* Done. */
	*p_port = &fport->base;

	PJ_LOG(4,(THIS_FILE, 
		  "MP3 file writer '%.*s' created: samp.rate=%dKHz, "
		  "bitrate=%dkbps%s, quality=%d",
		  (int)fport->base.info.name.slen,
		  fport->base.info.name.ptr,
		  PJMEDIA_PIA_SRATE(&fport->base.info),
		  fport->mp3_option.bit_rate/1000,
		  (fport->mp3_option.vbr ? " (VBR)" : ""),
		  fport->mp3_option.quality));

	return PJ_SUCCESS;
}



/*
 * Register callback.
 */
PJ_DEF(pj_status_t) 
pjmedia_mp3_writer_port_set_cb( pjmedia_port *port,
				pj_size_t pos,
				void *user_data,
					pj_status_t (*cb)(pjmedia_port *port,
						  void *usr_data))
{
	struct mp3_file_port *fport;

	/* Sanity check */
	PJ_ASSERT_RETURN(port && cb, PJ_EINVAL);

	/* Check that this is really a writer port */
	PJ_ASSERT_RETURN(port->info.signature == SIGNATURE, PJ_EINVALIDOP);

	fport = (struct mp3_file_port*) port;

	fport->cb_size = pos;
	fport->base.port_data.pdata = user_data;
	fport->cb = cb;

	return PJ_SUCCESS;

}


/*
 * Put a frame into the buffer. When the buffer is full, flush the buffer
 * to the file.
 */
static pj_status_t file_put_frame(pjmedia_port *this_port, 
				  const pjmedia_frame *frame)
{
	struct mp3_file_port *fport = (struct mp3_file_port *)this_port;
	unsigned long MP3Err;
	pj_ssize_t	bytes;
	pj_status_t status;
	unsigned long WriteSize;

	/* Record silence if input is no-frame */
	if (frame->type == PJMEDIA_FRAME_TYPE_NONE || frame->size == 0) {
	unsigned samples_left = PJMEDIA_PIA_SPF(&fport->base.info);
	unsigned samples_copied = 0;

	/* Only want to record at most 1 second of silence */
	if (fport->silence_duration >= PJMEDIA_PIA_SRATE(&fport->base.info))
		return PJ_SUCCESS;

	while (samples_left) {
		unsigned samples_needed = fport->mp3_samples_per_frame -
					  fport->mp3_sample_pos;
		if (samples_needed > samples_left)
		samples_needed = samples_left;

		pjmedia_zero_samples(fport->mp3_sample_buf + fport->mp3_sample_pos,
				 samples_needed);
		fport->mp3_sample_pos += samples_needed;
		samples_left -= samples_needed;
		samples_copied += samples_needed;

		/* Encode if we have full frame */
		if (fport->mp3_sample_pos == fport->mp3_samples_per_frame) {
		
		/* Clear position */
		fport->mp3_sample_pos = 0;

		/* Encode ! */
		MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream,
						fport->mp3_samples_per_frame,
						fport->mp3_sample_buf, 
						fport->mp3_buf, 
						&WriteSize);
		if (MP3Err != BE_ERR_SUCCESSFUL)
			return PJMEDIA_ERROR;

		/* Write the chunk */
		bytes = WriteSize;
		status=internal_file_write(fport->fd, fport->mp3_buf, &bytes, this_port);
		if (status != PJ_SUCCESS)
			return status;

		/* Increment total written. */
		fport->total += bytes;
		}
	}

	fport->silence_duration += PJMEDIA_PIA_SPF(&fport->base.info);

	}
	/* If encoder is expecting different sample size, then we need to
	 * buffer the samples.
	 */
	else if (fport->mp3_samples_per_frame != 
		 PJMEDIA_PIA_SPF(&fport->base.info)) 
	{
	unsigned samples_left = frame->size / 2;
	unsigned samples_copied = 0;
	const pj_int16_t *src_samples = frame->buf;

	fport->silence_duration = 0;

	while (samples_left) {
		unsigned samples_needed = fport->mp3_samples_per_frame -
					  fport->mp3_sample_pos;
		if (samples_needed > samples_left)
		samples_needed = samples_left;

		pjmedia_copy_samples(fport->mp3_sample_buf + fport->mp3_sample_pos,
				 src_samples + samples_copied,
				 samples_needed);
		fport->mp3_sample_pos += samples_needed;
		samples_left -= samples_needed;
		samples_copied += samples_needed;

		/* Encode if we have full frame */
		if (fport->mp3_sample_pos == fport->mp3_samples_per_frame) {
		
		/* Clear position */
		fport->mp3_sample_pos = 0;

		/* Encode ! */
		MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream,
						fport->mp3_samples_per_frame,
						fport->mp3_sample_buf, 
						fport->mp3_buf, 
						&WriteSize);
		if (MP3Err != BE_ERR_SUCCESSFUL)
			return PJMEDIA_ERROR;

		/* Write the chunk */
		bytes = WriteSize;
		status=internal_file_write(fport->fd, fport->mp3_buf, &bytes, this_port);
		if (status != PJ_SUCCESS)
			return status;

		/* Increment total written. */
		fport->total += bytes;
		}
	}

	} else {

	fport->silence_duration = 0;

	/* Encode ! */
	MP3Err = BladeDLL.beEncodeChunk(fport->mp3_stream,
					fport->mp3_samples_per_frame,
					frame->buf, 
					fport->mp3_buf, 
					&WriteSize);
	if (MP3Err != BE_ERR_SUCCESSFUL)
		return PJMEDIA_ERROR;

	/* Write the chunk */
	bytes = WriteSize;
	status=internal_file_write(fport->fd, fport->mp3_buf, &bytes, this_port);
	if (status != PJ_SUCCESS)
		return status;

	/* Increment total written. */
	fport->total += bytes;
	}

	/* Increment total written, and check if we need to call callback */
	
	if (fport->cb && fport->total >= fport->cb_size) {
	pj_status_t (*cb)(pjmedia_port*, void*);
	pj_status_t status;

	cb = fport->cb;
	fport->cb = NULL;

	status = (*cb)(this_port, this_port->port_data.pdata);
	return status;
	}

	return PJ_SUCCESS;
}

/*
 * Get frame, basically is a no-op operation.
 */
static pj_status_t file_get_frame(pjmedia_port *this_port, 
				  pjmedia_frame *frame)
{
	PJ_UNUSED_ARG(this_port);
	PJ_UNUSED_ARG(frame);
	return PJ_EINVALIDOP;
}


/*
 * Close the port, modify file header with updated file length.
 */
static pj_status_t file_on_destroy(pjmedia_port *this_port)
{
	struct mp3_file_port *fport = (struct mp3_file_port*)this_port;
	pj_status_t status;
	unsigned long WriteSize;
	unsigned long MP3Err;


	/* Close encoder */
	MP3Err = BladeDLL.beDeinitStream(fport->mp3_stream, fport->mp3_buf, 
					 &WriteSize);
	if (MP3Err == BE_ERR_SUCCESSFUL) {
	pj_ssize_t bytes = WriteSize;
	status=internal_file_write(fport->fd, fport->mp3_buf, &bytes, this_port);
	}

#ifdef WRITER_USES_THREAD
	fport->writer_finish=PJ_TRUE;
	while (!fport->writer_finished) {
		pj_thread_sleep(10);
	}
	pj_thread_destroy(fport->writer_thread);
	pj_mutex_destroy(fport->writer_buffers_mutex);
#endif

	/* Close file */
	status = pj_file_close(fport->fd);

	/* Write additional VBR header */
	if (fport->mp3_option.vbr) {
	MP3Err = BladeDLL.beWriteVBRHeader(fport->mp3_filename.ptr);
	}


	/* Decrement DLL reference counter */
	deinit_blade_dll();

	/* Done. */
	return PJ_SUCCESS;
}

static pj_status_t internal_file_write(pj_oshandle_t fd, const void *data, pj_ssize_t *size, pjmedia_port *this_port)
{
#ifndef WRITER_USES_THREAD
	return pj_file_write(fd,data,size);
#else
	pj_status_t status;
	void*		curBuff;
	struct write_buff	*list_node;
	struct mp3_file_port *fport=(struct mp3_file_port*)this_port;
	curBuff=malloc(*size);
	list_node=malloc(sizeof(struct write_buff));
	if (!curBuff||!list_node) {
		if (curBuff) free(curBuff);
		if (list_node) free(list_node);
		return PJ_ENOMEM;
	}
	status=pj_mutex_lock(fport->writer_buffers_mutex);
	if (status!=PJ_SUCCESS) {
		free(curBuff);
		free(list_node);
		return status;
	}
	memcpy(curBuff, data, *size);
	list_node->buffer=curBuff;
	list_node->size=*size;
	pj_list_push_back(&fport->writer_buffers, list_node);
	pj_mutex_unlock(fport->writer_buffers_mutex);
	return PJ_SUCCESS;
#endif
}


#ifdef WRITER_USES_THREAD
static	void*	threaded_writer(pjmedia_port *this_port)
{
	pj_status_t status;
	struct mp3_file_port *fport=(struct mp3_file_port *)this_port;
	fport->writer_finish=PJ_FALSE;
	fport->writer_finished=PJ_FALSE;
	while (1) {
		pj_ssize_t	size;
		void* curBuf=0;
		status=pj_mutex_lock(fport->writer_buffers_mutex);
		if (!pj_list_empty(&fport->writer_buffers)) {
			struct write_buff* list_buff=fport->writer_buffers.next;
			curBuf=list_buff->buffer;
			size=list_buff->size;
			pj_list_erase(list_buff);
			free(list_buff);
		} else if (fport->writer_finish==PJ_TRUE) {
			pj_mutex_unlock(fport->writer_buffers_mutex);
			break;
		}
		pj_mutex_unlock(fport->writer_buffers_mutex);
		if (curBuf) {
			pj_file_write(fport->fd, curBuf, &size);
			free(curBuf);
		} else pj_thread_sleep(20);
	}
	fport->writer_finished=PJ_TRUE;
	return 0;
}
#endif
_______________________________________________
Visit our blog: http://blog.pjsip.org

pjsip mailing list
pjsip@xxxxxxxxxxxxxxx
http://lists.pjsip.org/mailman/listinfo/pjsip_lists.pjsip.org

[Index of Archives]     [Asterisk Users]     [Asterisk App Development]     [Linux ARM Kernel]     [Linux ARM]     [Linux Omap]     [Fedora ARM]     [IETF Annouce]     [Security]     [Bugtraq]     [Linux]     [Linux OMAP]     [Linux MIPS]     [Linux API]
  Powered by Linux