wolffd@0: function [Y,FS,NBITS,OPTS] = mp3read(FILE,N,MONO,DOWNSAMP,DELAY) wolffd@0: % MP3READ Read MP3 audio file via use of external binaries. wolffd@0: % Y = MP3READ(FILE) reads an mp3-encoded audio file into the wolffd@0: % vector Y just like wavread reads a wav-encoded file (one channel wolffd@0: % per column). Extension ".mp3" is added if FILE has none. wolffd@0: % Also accepts other formats of wavread, such as wolffd@0: % Y = MP3READ(FILE,N) to read just the first N sample frames (N wolffd@0: % scalar), or the frames from N(1) to N(2) if N is a two-element vector. wolffd@0: % Y = MP3READ(FILE,FMT) or Y = mp3read(FILE,N,FMT) wolffd@0: % with FMT as 'native' returns int16 samples instead of doubles; wolffd@0: % FMT can be 'double' for default behavior (to exactly mirror the wolffd@0: % syntax of wavread). wolffd@0: % wolffd@0: % [Y,FS,NBITS,OPTS] = MP3READ(FILE...) returns extra information: wolffd@0: % FS is the sampling rate, NBITS is the bit depth (always 16), wolffd@0: % OPTS.fmt is a format info string; OPTS has multiple other wolffd@0: % fields, see WAVREAD. wolffd@0: % wolffd@0: % SIZ = MP3READ(FILE,'size') returns the size of the audio data contained wolffd@0: % in the file in place of the actual audio data, returning the wolffd@0: % 2-element vector SIZ=[samples channels]. wolffd@0: % wolffd@0: % [Y...] = MP3READ(FILE,N,MONO,DOWNSAMP,DELAY) extends the wolffd@0: % WAVREAD syntax to allow access to special features of the wolffd@0: % mpg123 engine: MONO = 1 forces output to be mono (by wolffd@0: % averaging stereo channels); DOWNSAMP = 2 or 4 downsamples by wolffd@0: % a factor of 2 or 4 (thus FS returns as 22050 or 11025 wolffd@0: % respectively for a 44 kHz mp3 file); wolffd@0: % To accommodate a bug in mpg123-0.59, DELAY controls how many wolffd@0: % "warm up" samples to drop at the start of the file; the wolffd@0: % default value of 2257 makes an mp3write/mp3read loop for a 44 wolffd@0: % kHz mp3 file be as close as possible to being temporally wolffd@0: % aligned; specify as 0 to prevent discard of initial samples. wolffd@0: % For later versions of mpg123 (e.g. 1.9.0) this is not needed; wolffd@0: % a flag in mp3read.m makes the default DELAY zero in this case. wolffd@0: % wolffd@0: % [Y...] = MP3READ(URL...) uses the built-in network wolffd@0: % functionality of mpg123 to read an MP3 file across the wolffd@0: % network. URL must be of the form 'http://...' or wolffd@0: % 'ftp://...'. 'size' and OPTS are not available in this mode. wolffd@0: % wolffd@0: % Example: wolffd@0: % To read an mp3 file as doubles at its original width and sampling rate: wolffd@0: % [Y,FS] = mp3read('piano.mp3'); wolffd@0: % To read the first 1 second of the same file, downsampled by a wolffd@0: % factor of 4, cast to mono, using the default filename wolffd@0: % extension: wolffd@0: % [Y,FS4] = mp3read('piano', FS/4, 1, 4); wolffd@0: % wolffd@0: % Note: Because the mp3 format encodes samples in blocks of 26 ms (at wolffd@0: % 44 kHz), and because of the "warm up" period of the encoder, wolffd@0: % the file length may not be exactly what you expect, depending wolffd@0: % on your version of mpg123 (recent versions fix warmup). wolffd@0: % wolffd@0: % Note: requires external binaries mpg123 and mp3info; you wolffd@0: % can find binaries for several platforms at: wolffd@0: % http://labrosa.ee.columbia.edu/matlab/mp3read.html wolffd@0: % wolffd@0: % See also mp3write, wavread. wolffd@0: wolffd@0: % $Header: /Users/dpwe/matlab/columbiafns/RCS/mp3read.m,v 1.7 2010/04/09 18:13:00 dpwe Exp dpwe $ wolffd@0: wolffd@0: % 2003-07-20 dpwe@ee.columbia.edu This version calls mpg123. wolffd@0: % 2004-08-31 Fixed to read whole files correctly wolffd@0: % 2004-09-08 Uses mp3info to get info about mp3 files too wolffd@0: % 2004-09-18 Reports all mp3info fields in OPTS.fmt; handles MPG2LSF sizes wolffd@0: % + added MONO, DOWNSAMP flags, changed default behavior. wolffd@0: % 2005-09-28 Fixed bug reading full-rate stereo as 1ch (thx bjoerns@vjk.dk) wolffd@0: % 2006-09-17 Chop off initial 2257 sample delay (for 44.1 kHz mp3) wolffd@0: % so read-write loop doesn't get progressively delayed. wolffd@0: % You can suppress this with a 5th argument of 0. wolffd@0: % 2007-02-04 Added support for FMT argument to match wavread wolffd@0: % Added automatic selection of binary etc. to allow it wolffd@0: % to work cross-platform without editing prior to wolffd@0: % submitting to Matlab File Exchange wolffd@0: % 2007-07-23 Tweaks to 'size' mode so it exactly agrees with read data. wolffd@0: % 2009-03-15 Added fixes so 'http://...' file URLs will work. wolffd@0: % 2009-03-26 Added filename length check to http: test (thx fabricio guzman) wolffd@0: wolffd@0: % find our baseline directory wolffd@0: path = fileparts(which('mp3read')); wolffd@0: wolffd@0: % %%%%% Directory for temporary file (if needed) wolffd@0: % % Try to read from environment, or use /tmp if it exists, or use CWD wolffd@0: tmpdir = getenv('TMPDIR'); wolffd@0: if isempty(tmpdir) || exist(tmpdir,'file')==0 wolffd@0: tmpdir = '/tmp'; wolffd@0: end wolffd@0: if exist(tmpdir,'file')==0 wolffd@0: tmpdir = ''; wolffd@0: end wolffd@0: % ensure it exists wolffd@0: %if length(tmpdir) > 0 && exist(tmpdir,'file')==0 wolffd@0: % mkdir(tmpdir); wolffd@0: %end wolffd@0: wolffd@0: %%%%%% Command to delete temporary file (if needed) wolffd@0: rmcmd = 'rm'; wolffd@0: wolffd@0: %%%%%% Location of the binaries - attempt to choose automatically wolffd@0: %%%%%% (or edit to be hard-coded for your installation) wolffd@0: ext = lower(computer); wolffd@0: if ispc wolffd@0: ext = 'exe'; wolffd@0: rmcmd = 'del'; wolffd@0: end wolffd@0: % mpg123-0.59 inserts silence at the start of decoded files, which wolffd@0: % we compensate. However, this is fixed in mpg123-1.9.0, so wolffd@0: % make this flag 1 only if you have mpg123-0.5.9 wolffd@0: MPG123059 = 0; wolffd@0: mpg123 = fullfile(path,['mpg123.',ext]); wolffd@0: mp3info = fullfile(path,['mp3info.',ext]); wolffd@0: wolffd@0: %%%%% Check for network mode wolffd@0: if length(FILE) > 6 && (strcmp(lower(FILE(1:7)),'http://') == 1 ... wolffd@0: || strcmp(lower(FILE(1:6)),'ftp://')) wolffd@0: % mp3info not available over network wolffd@0: OVERNET = 1; wolffd@0: else wolffd@0: OVERNET = 0; wolffd@0: end wolffd@0: wolffd@0: wolffd@0: %%%%% Process input arguments wolffd@0: if nargin < 2 wolffd@0: N = 0; wolffd@0: end wolffd@0: wolffd@0: % Check for FMT spec (per wavread) wolffd@0: FMT = 'double'; wolffd@0: if ischar(N) wolffd@0: FMT = lower(N); wolffd@0: N = 0; wolffd@0: end wolffd@0: wolffd@0: if length(N) == 1 wolffd@0: % Specified N was upper limit wolffd@0: N = [1 N]; wolffd@0: end wolffd@0: if nargin < 3 wolffd@0: forcemono = 0; wolffd@0: else wolffd@0: % Check for 3rd arg as FMT wolffd@0: if ischar(MONO) wolffd@0: FMT = lower(MONO); wolffd@0: MONO = 0; wolffd@0: end wolffd@0: forcemono = (MONO ~= 0); wolffd@0: end wolffd@0: if nargin < 4 wolffd@0: downsamp = 1; wolffd@0: else wolffd@0: downsamp = DOWNSAMP; wolffd@0: end wolffd@0: if downsamp ~= 1 && downsamp ~= 2 && downsamp ~= 4 wolffd@0: error('DOWNSAMP can only be 1, 2, or 4'); wolffd@0: end wolffd@0: wolffd@0: % process DELAY option (nargin 5) after we've read the SR wolffd@0: wolffd@0: if strcmp(FMT,'native') == 0 && strcmp(FMT,'double') == 0 && ... wolffd@0: strcmp(FMT,'size') == 0 wolffd@0: error(['FMT must be ''native'' or ''double'' (or ''size''), not ''',FMT,'''']); wolffd@0: end wolffd@0: wolffd@0: wolffd@0: %%%%%% Constants wolffd@0: NBITS=16; wolffd@0: wolffd@0: %%%%% add extension if none (like wavread) wolffd@0: [path,file,ext] = fileparts(FILE); wolffd@0: if isempty(ext) wolffd@0: FILE = [FILE, '.mp3']; wolffd@0: end wolffd@0: wolffd@0: %%%%% maybe expand ~ %%%%%% wolffd@0: if FILE(1) == '~' wolffd@0: FILE = [getenv('HOME'),FILE(2:end)]; wolffd@0: end wolffd@0: wolffd@0: wolffd@0: if ~OVERNET wolffd@0: %%%%%% Probe file to find format, size, etc. using "mp3info" utility wolffd@0: cmd = ['"',mp3info, '" -r m -p "%Q %u %b %r %v * %C %e %E %L %O %o %p" "', FILE,'"']; wolffd@0: % Q = samprate, u = #frames, b = #badframes (needed to get right answer from %u) wolffd@0: % r = bitrate, v = mpeg version (1/2/2.5) wolffd@0: % C = Copyright, e = emph, E = CRC, L = layer, O = orig, o = mono, p = pad wolffd@0: w = mysystem(cmd); wolffd@0: % Break into numerical and ascii parts by finding the delimiter we put in wolffd@0: starpos = findstr(w,'*'); wolffd@0: nums = str2num(w(1:(starpos - 2))); wolffd@0: strs = tokenize(w((starpos+2):end)); wolffd@0: wolffd@0: SR = nums(1); wolffd@0: nframes = nums(2); wolffd@0: nchans = 2 - strcmp(strs{6}, 'mono'); wolffd@0: layer = length(strs{4}); wolffd@0: bitrate = nums(4)*1000; wolffd@0: mpgv = nums(5); wolffd@0: % Figure samples per frame, after wolffd@0: % http://board.mp3-tech.org/view.php3?bn=agora_mp3techorg&key=1019510889 wolffd@0: if layer == 1 wolffd@0: smpspfrm = 384; wolffd@0: elseif SR < 32000 && layer ==3 wolffd@0: smpspfrm = 576; wolffd@0: if mpgv == 1 wolffd@0: error('SR < 32000 but mpeg version = 1'); wolffd@0: end wolffd@0: else wolffd@0: smpspfrm = 1152; wolffd@0: end wolffd@0: wolffd@0: OPTS.fmt.mpgBitrate = bitrate; wolffd@0: OPTS.fmt.mpgVersion = mpgv; wolffd@0: % fields from wavread's OPTS wolffd@0: OPTS.fmt.nAvgBytesPerSec = bitrate/8; wolffd@0: OPTS.fmt.nSamplesPerSec = SR; wolffd@0: OPTS.fmt.nChannels = nchans; wolffd@0: OPTS.fmt.nBlockAlign = smpspfrm/SR*bitrate/8; wolffd@0: OPTS.fmt.nBitsPerSample = NBITS; wolffd@0: OPTS.fmt.mpgNFrames = nframes; wolffd@0: OPTS.fmt.mpgCopyright = strs{1}; wolffd@0: OPTS.fmt.mpgEmphasis = strs{2}; wolffd@0: OPTS.fmt.mpgCRC = strs{3}; wolffd@0: OPTS.fmt.mpgLayer = strs{4}; wolffd@0: OPTS.fmt.mpgOriginal = strs{5}; wolffd@0: OPTS.fmt.mpgChanmode = strs{6}; wolffd@0: OPTS.fmt.mpgPad = strs{7}; wolffd@0: OPTS.fmt.mpgSampsPerFrame = smpspfrm; wolffd@0: else wolffd@0: % OVERNET mode wolffd@0: OPTS = []; wolffd@0: % guesses wolffd@0: smpspfrm = 1152; wolffd@0: SR = 44100; wolffd@0: nframes = 0; wolffd@0: end wolffd@0: wolffd@0: if SR == 16000 && downsamp == 4 wolffd@0: error('mpg123 will not downsample 16 kHz files by 4 (only 2)'); wolffd@0: end wolffd@0: wolffd@0: % from libmpg123/frame.h wolffd@0: GAPLESS_DELAY = 529; wolffd@0: wolffd@0: % process or set delay wolffd@0: if nargin < 5 wolffd@0: wolffd@0: if MPG123059 wolffd@0: mpg123delay44kHz = 2257; % empirical delay of lame/mpg123 loop wolffd@0: mpg123delay16kHz = 1105; % empirical delay of lame/mpg123 loop wolffd@0: % for 16 kHz sampling - one 1152 wolffd@0: % sample frame less?? wolffd@0: if SR == 16000 wolffd@0: rawdelay = mpg123delay16kHz; wolffd@0: else wolffd@0: rawdelay = mpg123delay44kHz; % until we know better wolffd@0: end wolffd@0: delay = round(rawdelay/downsamp); wolffd@0: else wolffd@0: % seems like predelay is fixed in mpg123-1.9.0 wolffd@0: delay = 0; wolffd@0: end wolffd@0: else wolffd@0: delay = DELAY; wolffd@0: end wolffd@0: wolffd@0: if downsamp == 1 wolffd@0: downsampstr = ''; wolffd@0: else wolffd@0: downsampstr = [' -',num2str(downsamp)]; wolffd@0: end wolffd@0: FS = SR/downsamp; wolffd@0: wolffd@0: if forcemono == 1 wolffd@0: nchans = 1; wolffd@0: chansstr = ' -m'; wolffd@0: else wolffd@0: chansstr = ''; wolffd@0: end wolffd@0: wolffd@0: % Size-reading version wolffd@0: if strcmp(FMT,'size') == 1 wolffd@0: if MPG123059 wolffd@0: Y = [floor(smpspfrm*nframes/downsamp)-delay, nchans]; wolffd@0: else wolffd@0: Y = [floor(smpspfrm*nframes/downsamp)-GAPLESS_DELAY, nchans]; wolffd@0: end wolffd@0: else wolffd@0: wolffd@0: % Temporary file to use wolffd@0: tmpfile = fullfile(tmpdir, ['tmp',num2str(round(1000*rand(1))),'.wav']); wolffd@0: wolffd@0: skipx = 0; wolffd@0: skipblks = 0; wolffd@0: skipstr = ''; wolffd@0: sttfrm = N(1)-1; wolffd@0: wolffd@0: % chop off transcoding delay? wolffd@0: %sttfrm = sttfrm + delay; % empirically measured wolffd@0: % no, we want to *decode* those samples, then drop them wolffd@0: % so delay gets added to skipx instead wolffd@0: wolffd@0: if sttfrm > 0 wolffd@0: skipblks = floor(sttfrm*downsamp/smpspfrm); wolffd@0: skipx = sttfrm - (skipblks*smpspfrm/downsamp); wolffd@0: skipstr = [' -k ', num2str(skipblks)]; wolffd@0: end wolffd@0: skipx = skipx + delay; wolffd@0: wolffd@0: lenstr = ''; wolffd@0: endfrm = -1; wolffd@0: decblk = 0; wolffd@0: if length(N) > 1 wolffd@0: endfrm = N(2); wolffd@0: if endfrm > sttfrm wolffd@0: decblk = ceil((endfrm+delay)*downsamp/smpspfrm) - skipblks + 10; wolffd@0: % we read 10 extra blks (+10) to cover the case where up to 10 bad wolffd@0: % blocks are included in the part we are trying to read (it happened) wolffd@0: lenstr = [' -n ', num2str(decblk)]; wolffd@0: % This generates a spurious "Warn: requested..." if reading right wolffd@0: % to the last sample by index (or bad blks), but no matter. wolffd@0: end wolffd@0: end wolffd@0: wolffd@0: % Run the decode wolffd@0: cmd=['"',mpg123,'"', downsampstr, chansstr, skipstr, lenstr, ... wolffd@0: ' -q -w "', tmpfile,'" "',FILE,'"']; wolffd@0: %w = wolffd@0: mysystem(cmd); wolffd@0: wolffd@0: % Load the data (may update FS if it was based on a guess previously) wolffd@0: [Y,FS] = wavread(tmpfile); wolffd@0: wolffd@0: % % pad delay on to end, just in case wolffd@0: % Y = [Y; zeros(delay,size(Y,2))]; wolffd@0: % % no, the saved file is just longer wolffd@0: wolffd@0: if decblk > 0 && length(Y) < decblk*smpspfrm/downsamp wolffd@0: % This will happen if the selected block range includes >1 bad block wolffd@0: disp(['Warn: requested ', num2str(decblk*smpspfrm/downsamp),' frames, returned ',num2str(length(Y))]); wolffd@0: end wolffd@0: wolffd@0: % Delete tmp file wolffd@0: mysystem([rmcmd,' "', tmpfile,'"']); wolffd@0: wolffd@0: % debug wolffd@0: % disp(['sttfrm=',num2str(sttfrm),' endfrm=',num2str(endfrm),' skipx=',num2str(skipx),' delay=',num2str(delay),' len=',num2str(length(Y))]); wolffd@0: wolffd@0: % Select the desired part wolffd@0: if skipx+endfrm-sttfrm > length(Y) wolffd@0: endfrm = length(Y)+sttfrm-skipx; wolffd@0: end wolffd@0: wolffd@0: if endfrm > sttfrm wolffd@0: Y = Y(skipx+(1:(endfrm-sttfrm)),:); wolffd@0: elseif skipx > 0 wolffd@0: Y = Y((skipx+1):end,:); wolffd@0: end wolffd@0: wolffd@0: % Convert to int if format = 'native' wolffd@0: if strcmp(FMT,'native') wolffd@0: Y = int16((2^15)*Y); wolffd@0: end wolffd@0: wolffd@0: end wolffd@0: wolffd@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% wolffd@0: function w = mysystem(cmd) wolffd@0: % Run system command; report error; strip all but last line wolffd@0: [s,w] = system(cmd); wolffd@0: if s ~= 0 wolffd@0: error(['unable to execute ',cmd,' (',w,')']); wolffd@0: end wolffd@0: % Keep just final line wolffd@0: w = w((1+max([0,findstr(w,10)])):end); wolffd@0: % Debug wolffd@0: %disp([cmd,' -> ','*',w,'*']); wolffd@0: wolffd@0: %%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%%% wolffd@0: function a = tokenize(s,t) wolffd@0: % Break space-separated string into cell array of strings. wolffd@0: % Optional second arg gives alternate separator (default ' ') wolffd@0: % 2004-09-18 dpwe@ee.columbia.edu wolffd@0: if nargin < 2; t = ' '; end wolffd@0: a = []; wolffd@0: p = 1; wolffd@0: n = 1; wolffd@0: l = length(s); wolffd@0: nss = findstr([s(p:end),t],t); wolffd@0: for ns = nss wolffd@0: % Skip initial spaces (separators) wolffd@0: if ns == p wolffd@0: p = p+1; wolffd@0: else wolffd@0: if p <= l wolffd@0: a{n} = s(p:(ns-1)); wolffd@0: n = n+1; wolffd@0: p = ns+1; wolffd@0: end wolffd@0: end wolffd@0: end wolffd@0: