/* * audiocnvt.c * * Routines to convert RAW audio to/from various audio formats * * (C) 1997 Randall Hopper * * Redistribution and use in source and binary forms, with or without * modification, are permitted provided that the following conditions are * met: 1. Redistributions of source code must retain the above copyright * notice, this list of conditions and the following disclaimer. 2. * Redistributions in binary form must reproduce the above copyright notice, * this list of conditions and the following disclaimer in the documentation * and/or other materials provided with the distribution. * * THIS SOFTWARE IS PROVIDED BY THE AUTHOR AND CONTRIBUTORS ``AS IS'' AND ANY * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE * DISCLAIMED. IN NO EVENT SHALL THE AUTHOR OR CONTRIBUTORS BE LIABLE FOR * ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR * SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER * CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT * LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY * OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF * SUCH DAMAGE. * */ /* ******************** Include Files ************** */ #include #include #include #include #include #include "tvdefines.h" #include "audiocnvt.h" #include "glob.h" #include "app_rsrc.h" /* ******************** Local defines ************** */ typedef struct { int in_fd; char *in_fname; TV_SOUND_FMT *in_fmt; int out_fd; char *out_fname; TV_SOUND_FMT *out_fmt; XUTIL_RUNCMD_CANCELCB *cancel_test; void *cancel_cb_data; XUTIL_RUNCMD_DONECB *done_cb; void *done_cb_data; } TV_AUDIO_ARGPACK; typedef struct { TV_SOUND_FMT fmt_in; TV_SOUND_FMT fmt_out; char fname_in [ MAXPATHLEN ], fname_tmp[ MAXPATHLEN ], fname_out[ MAXPATHLEN ]; char **cmd; /* Cmd running and its args */ char *tmpstr; XUTIL_RUNCMD_CANCELCB *cancel_test; void *cancel_cb_data; XUTIL_RUNCMD_DONECB *done_cb; void *done_cb_data; } TV_AUDIO_CMD_STATE; typedef struct { TV_AUDIO_FILE_FMT fmt; char *sox_opt; } TV_FFMT_ITEM_DEF; typedef struct { TV_AUDIO_SAMPLE_FMT fmt; char *sox_opt; } TV_SFMT_ITEM_DEF; /* ******************** Private variables ************** */ static TV_FFMT_ITEM_DEF Ffmt_item_def[] = { { TV_AUDIO_FILE_FMT_RAW , NULL }, { TV_AUDIO_FILE_FMT_SUNAU , "-t raw -U -b -c 1 -r 8012" }, { TV_AUDIO_FILE_FMT_WAV , "-t wav" }, { TV_AUDIO_FILE_FMT_VOC , "-t voc" }, { TV_AUDIO_FILE_FMT_AIFF , "-t aiff" }, { TV_AUDIO_FILE_FMT_MPEG2 , "-t aiff" }, /* precond */ { TV_AUDIO_FILE_FMT_MPEG3 , "-t aiff" }, /* precond */ }; static TV_SFMT_ITEM_DEF Sfmt_item_def[] = { { TV_AUDIO_SAMPLE_FMT_MULAW_U8 , "-t raw -U -b" }, { TV_AUDIO_SAMPLE_FMT_LIN_S8 , "-t raw -s -b" }, { TV_AUDIO_SAMPLE_FMT_LIN_U8 , "-t raw -u -b" }, { TV_AUDIO_SAMPLE_FMT_LIN_S16_LE, "-t raw -s -w" }, { TV_AUDIO_SAMPLE_FMT_LIN_U16_LE, "-t raw -u -w" }, { TV_AUDIO_SAMPLE_FMT_LIN_S16_BE, "-t raw -s -w -x" }, { TV_AUDIO_SAMPLE_FMT_LIN_U16_BE, "-t raw -u -w -x" } }; /* ******************** Forward declarations ************** */ /* ******************** Function Definitions ************** */ /* DoCmdFailDialog - Display a dialog citing the command that failed */ /* and its exit status; wait on user to dismiss. */ static void DoCmdFailDialog( char *cmd[], int status ) { char msg[ 2*MAXPATHLEN + 160 ]; TV_INT32 i; sprintf( msg, "Audio conversion failed.\nCMD = " ); for ( i = 0; cmd[i] != NULL; i++ ) sprintf( msg+strlen(msg), "%s ", cmd[i] ); sprintf( msg+strlen(msg), "\nSTATUS = 0x%.4x", status ); XUTILDialogPause( TVTOPLEVEL, "Error", msg, TV_DIALOG_TYPE_OK ); } /* GetFileFmtDef - Return the file format definition record */ static TV_FFMT_ITEM_DEF *GetFileFmtDef( TV_AUDIO_FILE_FMT fmt ) { TV_INT32 i; for ( i = 0; i < XtNumber( Ffmt_item_def ); i++ ) if ( Ffmt_item_def[i].fmt == fmt ) break; if ( i >= XtNumber( Ffmt_item_def ) ) { fprintf( stderr, "audiocnvt.c:GetFileFmtDef: Bad format %d\n", fmt ); exit(1); } return &Ffmt_item_def[i]; } /* GetSampleFmtDef - Return the file format definition record */ static TV_SFMT_ITEM_DEF *GetSampleFmtDef( TV_AUDIO_SAMPLE_FMT fmt ) { TV_INT32 i; for ( i = 0; i < XtNumber( Sfmt_item_def ); i++ ) if ( Sfmt_item_def[i].fmt == fmt ) break; if ( i >= XtNumber( Sfmt_item_def ) ) { fprintf( stderr, "audiocnvt.c:GetSampleFmtDef: Bad format %d\n", fmt); exit(1); } return &Sfmt_item_def[i]; } /* TVAUDIOCNVTBuildSoundFmtSoxArgs - */ /* Determine the sox args describing the specified sound format. */ void TVAUDIOCNVTBuildSoundFmtSoxArgs( TV_SOUND_FMT *fmt, char args[] ) { TV_FFMT_ITEM_DEF *frec; TV_SFMT_ITEM_DEF *srec; if ( fmt->file_fmt == TV_AUDIO_FILE_FMT_RAW ) { srec = GetSampleFmtDef( fmt->samp_fmt ); sprintf( args, "%s -c %d -r %ld", srec->sox_opt, fmt->stereo ? 2 : 1, fmt->samp_rate ); } else { frec = GetFileFmtDef( fmt->file_fmt ); sprintf( args, "%s", frec->sox_opt ); } } /* MPEGEncodeDoneCB */ /* - Called when MPEG encode cmd completes or is aborted */ static void MPEGEncodeDoneCB( TV_BOOL aborted, int status, void *cb_data ) { TV_AUDIO_CMD_STATE *state = (TV_AUDIO_CMD_STATE *) cb_data; TV_BOOL failed = FALSE; /* If the command failed, tell the user about it */ if ( !aborted && ( status != 0 ) ) { failed = TRUE; DoCmdFailDialog( state->cmd, status ); } /* Do post-cmd cleanup */ free( state->cmd ); free( state->tmpstr ); unlink( state->fname_tmp ); if ( (aborted || failed) && ( state->fname_out[0] != '\0' ) ) unlink( state->fname_out ); /* Call user-supplied done callback */ state->done_cb( aborted, status, state->done_cb_data ); free( state ); return; } /* SoxDoneCB */ /* - Called when SOX cmd completes or is aborted. */ static void SoxDoneCB( TV_BOOL aborted, int status, void *cb_data ) { TV_AUDIO_CMD_STATE *state = (TV_AUDIO_CMD_STATE *) cb_data; TV_BOOL mpeg_enc, failed = FALSE; char shell_cmd[ 3*MAXPATHLEN ], *rec_cmd, *fname_out; TVUTIL_PIPE_END end[3] = {{ -1 }, { -1 }, { -1 }}; /* If the command failed, tell the user about it */ if ( !aborted && ( status != 0 ) ) { failed = TRUE; DoCmdFailDialog( state->cmd, status ); } mpeg_enc = (( state->fmt_out.file_fmt == TV_AUDIO_FILE_FMT_MPEG2 ) || ( state->fmt_out.file_fmt == TV_AUDIO_FILE_FMT_MPEG3 )); fname_out = ( mpeg_enc ? state->fname_tmp : state->fname_out ); /* Do post-cmd cleanup */ free( state->cmd ); free( state->tmpstr ); state->cmd = NULL; if ( (aborted || failed) && ( fname_out[0] != '\0' ) ) unlink( fname_out ); /* If aborted/failed or not going onto MPEG encoding, bail */ if ( (aborted || failed) || !mpeg_enc ) goto RETURN; /*********************************/ /* GOING ONTO MPEG-2/-3 ENCODE */ /*********************************/ /* Grab the right MPEG audio encoder cmd */ if ( state->fmt_out.file_fmt == TV_AUDIO_FILE_FMT_MPEG2 ) rec_cmd = App_res.rec_cmd_mpeg2; else rec_cmd = App_res.rec_cmd_mpeg3; if ( strspn( rec_cmd, " " ) == strlen( rec_cmd ) ) { XUTILDialogPause( TVTOPLEVEL, "Error", "No MPEG conv cmd.", TV_DIALOG_TYPE_OK ); goto RETURN; } /* Build cmd: '' '' */ sprintf( shell_cmd, "%s '%s' '%s'", rec_cmd, state->fname_tmp, state->fname_out ); TVUTILCmdStrToArgList( shell_cmd, &state->cmd, &state->tmpstr ); /* Exec MPEG encode cmd & wait on it to finish or user to cancel */ XUTILRunCmdAllowCancel( TVAPPCTX, state->cmd, end, state->cancel_test, state->cancel_cb_data, MPEGEncodeDoneCB , state ); RETURN: if ( (aborted || failed) || !mpeg_enc ) { /* Call user-supplied done callback */ state->done_cb( aborted, status, state->done_cb_data ); free( state ); } return; } /* TVAUDIOCNVTUsingSox */ /* Set up a pipe to convert data available on FD in_fd or in_fname in */ /* format in_fmt to output on FD out_fd or out_fname in format out_fmt. */ static void TVAUDIOCNVTUsingSox( TV_AUDIO_ARGPACK *a ) { char shell_cmd[ 2*MAXPATHLEN+80 ], in_args [ MAXPATHLEN+80 ], out_args [ MAXPATHLEN+80 ], dir_name [ MAXPATHLEN ], prefix [ MAXPATHLEN ], *out_fname, *p; TVUTIL_PIPE_END end[3] = {{ -1, FALSE }, { -1, FALSE }, { -1 }}; TV_AUDIO_CMD_STATE *state = NULL; TV_BOOL mpeg_enc; /* Peek at whether we're going to MPEG-2/-3 after this */ mpeg_enc = (( a->out_fmt->file_fmt == TV_AUDIO_FILE_FMT_MPEG2 ) || ( a->out_fmt->file_fmt == TV_AUDIO_FILE_FMT_MPEG3 )); /* Grab the "from" and "to" SOX sound format args */ TVAUDIOCNVTBuildSoundFmtSoxArgs( a->in_fmt , in_args ); TVAUDIOCNVTBuildSoundFmtSoxArgs( a->out_fmt, out_args ); /* Alloc state structure for use during command execution */ if ( (state = malloc( sizeof( *state ) )) == NULL ) TVUTILOutOfMemory(); memset( state, '\0', sizeof( *state ) ); /* Prepare file descriptors */ end[0].fd = ( a->in_fd >= 0 ) ? a->in_fd : -1; if ( !mpeg_enc ) { end[1].fd = ( a->out_fd >= 0 ) ? a->out_fd : -1; out_fname = a->out_fname; state->fname_tmp[0] = '\0'; } else { end[1].fd = -1; /* For MPEG encode, generate a tempfile in same dir as output file */ dir_name[0] = '\0'; strncat( dir_name, a->out_fname, sizeof( dir_name )-1 ); if ( (p = strrchr( dir_name, '/' )) == NULL ) { strcpy( dir_name , "." ); strcpy( prefix, a->out_fname ); } else { strcpy( prefix, p+1 ); if ( p == dir_name ) p++; *p = '\0'; } unsetenv( "TMPDIR" ); out_fname = tempnam( dir_name, prefix ); strcpy( state->fname_tmp, out_fname ); free( out_fname ); out_fname = state->fname_tmp; } /* Build Cmd: "sox '%s' '%s'" */ sprintf( shell_cmd, "sox %s '%s' %s '%s'", in_args, (end[0].fd >= 0) ? "-" : a->in_fname, out_args, (end[1].fd >= 0) ? "-" : out_fname ); TVUTILCmdStrToArgList( shell_cmd, &state->cmd, &state->tmpstr ); /* Execute conversion cmd & wait on it to finish or user to cancel. */ /* FIXME: Also displaying output of child as it runs might */ /* be useful. */ memcpy( &state->fmt_in , a->in_fmt , sizeof( state->fmt_in ) ); memcpy( &state->fmt_out, a->out_fmt, sizeof( state->fmt_out ) ); if ( a->in_fd == -1 ) strncat( state->fname_in , a->in_fname , sizeof( state->fname_in )-1); if ( a->out_fd == -1 ) strncat( state->fname_out, a->out_fname, sizeof( state->fname_out )-1); state->cancel_test = a->cancel_test; state->cancel_cb_data = a->cancel_cb_data; state->done_cb = a->done_cb; state->done_cb_data = a->done_cb_data; XUTILRunCmdAllowCancel( TVAPPCTX, state->cmd, end, state->cancel_test, state->cancel_cb_data, SoxDoneCB , state ); } /* TVAUDIOCNVTConvertFormat */ /* Set up a pipe to convert data available on FD in_fd in format in_fmt */ /* to output on FD out_fd in format out_fmt. */ void TVAUDIOCNVTConvertFormat( int in_fd, char in_fname[], TV_SOUND_FMT *in_fmt, int out_fd, char out_fname[], TV_SOUND_FMT *out_fmt, XUTIL_RUNCMD_CANCELCB *cancel_test, void *cancel_cb_data, XUTIL_RUNCMD_DONECB *done_cb, void *done_cb_data ) { TV_BOOL mpeg_enc, mpeg_dec; TV_AUDIO_ARGPACK a; /* Sanity checks */ if ( (( in_fd < 0 ) && ( in_fname == NULL )) || (( out_fd < 0 ) && ( out_fname == NULL )) ) { fprintf( stderr, "TVAUDIOCNVTConvertFormat: Bad args\n" ); exit(1); } mpeg_dec = (( in_fmt ->file_fmt == TV_AUDIO_FILE_FMT_MPEG2 ) || ( in_fmt ->file_fmt == TV_AUDIO_FILE_FMT_MPEG3 )); mpeg_enc = (( out_fmt->file_fmt == TV_AUDIO_FILE_FMT_MPEG2 ) || ( out_fmt->file_fmt == TV_AUDIO_FILE_FMT_MPEG3 )); /* "Not implemented" checks */ if ( mpeg_dec ) { fprintf( stderr, "TVAUDIOCNVTConvertFormat: Decoding from MPEG-2/3 not " "supported yet.\n" ); exit(1); } if ( mpeg_enc && (out_fd >= 0) ) { fprintf( stderr, "TVAUDIOCNVTConvertFormat: Encoding to MPEG-2/3 open " "file descriptor not supported yet.\n" ); exit(1); } /* Pack args into convenience struct */ a.in_fd = in_fd; a.in_fname = in_fname; a.in_fmt = in_fmt; a.out_fd = out_fd; a.out_fname = out_fname; a.out_fmt = out_fmt; a.cancel_test = cancel_test; a.cancel_cb_data = cancel_cb_data; a.done_cb = done_cb; a.done_cb_data = done_cb_data; /* First, see if input format is MPEG-2 or -3. If so, converting */ /* from that is the first step. */ /* FIXME: Add this when needed */ /* Ok, we've got input format we can feed SOX */ TVAUDIOCNVTUsingSox( &a ); }