📄 smf.c
字号:
/***************************************************************************** * smf.c : Standard MIDI File (.mid) demux module for vlc ***************************************************************************** * Copyright © 2007 Rémi Denis-Courmont * $Id$ * * 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., 51 Franklin Street, Fifth Floor, Boston MA 02110-1301, USA. *****************************************************************************/#ifdef HAVE_CONFIG_H# include "config.h"#endif#include <vlc_common.h>#include <vlc_plugin.h>#include <vlc_demux.h>#include <vlc_aout.h>#include <vlc_codecs.h>#include <vlc_charset.h>#include <limits.h>#include <assert.h>#define TEMPO_MIN 20#define TEMPO_MAX 250 /* Beats per minute */static int Open (vlc_object_t *);static void Close (vlc_object_t *);vlc_module_begin (); set_description (N_("SMF demuxer")); set_category (CAT_INPUT); set_subcategory (SUBCAT_INPUT_DEMUX); set_capability ("demux", 20); set_callbacks (Open, Close);vlc_module_end ();static int Demux (demux_t *);static int Control (demux_t *, int i_query, va_list args);typedef struct smf_track_t{ int64_t offset; /* Read offset in the file (stream_Tell) */ int64_t end; /* End offset in the file */ uint64_t next; /* Time of next message (in term of pulses) */ uint8_t running_event; /* Running (previous) event */} mtrk_t;static int ReadDeltaTime (stream_t *s, mtrk_t *track);struct demux_sys_t{ es_out_id_t *es; date_t pts; uint64_t pulse; /* Pulses counter */ unsigned ppqn; /* Pulses Per Quarter Note */ /* by the way, "quarter note" is "noire" in French */ unsigned trackc; /* Number of tracks */ mtrk_t trackv[0]; /* Track states */};/***************************************************************************** * Open: check file and initializes structures *****************************************************************************/static int Open (vlc_object_t * p_this){ demux_t *p_demux = (demux_t *)p_this; stream_t *stream = p_demux->s; demux_sys_t *p_sys; const uint8_t *peek; unsigned tracks, ppqn; bool multitrack; /* (Try to) parse the SMF header */ /* Header chunk always has 6 bytes payload */ if (stream_Peek (stream, &peek, 14) < 14) return VLC_EGENERIC; /* Skip RIFF MIDI header if present */ if (!memcmp (peek, "RIFF", 4) && !memcmp (peek + 8, "RMID", 4)) { uint32_t riff_len = GetDWLE (peek + 4); msg_Dbg (p_this, "detected RIFF MIDI file (%u bytes)", (unsigned)riff_len); if ((stream_Read (stream, NULL, 12) < 12)) return VLC_EGENERIC; /* Look for the RIFF data chunk */ for (;;) { char chnk_hdr[8]; uint32_t chnk_len; if ((riff_len < 8) || (stream_Read (stream, chnk_hdr, 8) < 8)) return VLC_EGENERIC; riff_len -= 8; chnk_len = GetDWLE (chnk_hdr + 4); if (riff_len < chnk_len) return VLC_EGENERIC; riff_len -= chnk_len; if (!memcmp (chnk_hdr, "data", 4)) break; /* found! */ if (stream_Read (stream, NULL, chnk_len) < (ssize_t)chnk_len) return VLC_EGENERIC; } /* Read real SMF header. Assume RIFF data chunk length is proper. */ if (stream_Peek (stream, &peek, 14) < 14) return VLC_EGENERIC; } if (memcmp (peek, "MThd\x00\x00\x00\x06", 8)) return VLC_EGENERIC; peek += 8; /* First word: SMF type */ switch (GetWBE (peek)) { case 0: multitrack = false; break; case 1: multitrack = true; break; default: /* We don't implement SMF2 (as do many) */ msg_Err (p_this, "unsupported SMF file type %u", GetWBE (peek)); return VLC_EGENERIC; } peek += 2; /* Second word: number of tracks */ tracks = GetWBE (peek); peek += 2; if (!multitrack && (tracks != 1)) { msg_Err (p_this, "invalid SMF type 0 file"); return VLC_EGENERIC; } msg_Dbg (p_this, "detected Standard MIDI File (type %u) with %u track(s)", multitrack, tracks); /* Third/last word: timing */ ppqn = GetWBE (peek); if (ppqn & 0x8000) { /* FIXME */ msg_Err (p_this, "SMPTE timestamps not implemented"); return VLC_EGENERIC; } else { msg_Dbg (p_this, " %u pulses per quarter note", ppqn); } p_sys = malloc (sizeof (*p_sys) + (sizeof (mtrk_t) * tracks)); if (p_sys == NULL) return VLC_ENOMEM; /* We've had a valid SMF header - now skip it*/ if (stream_Read (stream, NULL, 14) < 14) goto error; p_demux->pf_demux = Demux; p_demux->pf_control = Control; p_demux->p_sys = p_sys; /* Default SMF tempo is 120BPM, i.e. half a second per quarter note */ date_Init (&p_sys->pts, ppqn * 2, 1); date_Set (&p_sys->pts, 1); p_sys->pulse = 0; p_sys->ppqn = ppqn; p_sys->trackc = tracks; /* Prefetch track offsets */ for (unsigned i = 0; i < tracks; i++) { uint8_t head[8]; if (i > 0) { /* Seeking screws streaming up, but there is no way around this, * as SMF1 tracks are performed simultaneously. * Not a big deal as SMF1 are usually only a few kbytes anyway. */ if (stream_Seek (stream, p_sys->trackv[i-1].end)) { msg_Err (p_this, "cannot build SMF index (corrupted file?)"); goto error; } } for (;;) { stream_Read (stream, head, 8); if (memcmp (head, "MTrk", 4) == 0) break; msg_Dbg (p_this, "skipping unknown SMF chunk"); stream_Read (stream, NULL, GetDWBE (head + 4)); } p_sys->trackv[i].offset = stream_Tell (stream); p_sys->trackv[i].end = p_sys->trackv[i].offset + GetDWBE (head + 4); p_sys->trackv[i].next = 0; ReadDeltaTime (stream, p_sys->trackv + i); p_sys->trackv[i].running_event = 0xF6; /* Why 0xF6 (Tuning Calibration)? * Because it has zero bytes of data, so the parser will detect the * error if the first event uses running status. */ } es_format_t fmt; es_format_Init (&fmt, AUDIO_ES, VLC_FOURCC('M', 'I', 'D', 'I')); fmt.audio.i_channels = 2; fmt.audio.i_original_channels = fmt.audio.i_physical_channels = AOUT_CHAN_LEFT | AOUT_CHAN_RIGHT; fmt.audio.i_rate = 44100; /* dummy value */ p_sys->es = es_out_Add (p_demux->out, &fmt); return VLC_SUCCESS;error: free (p_sys); return VLC_EGENERIC;}/** * Releases allocate resources. */static void Close (vlc_object_t * p_this){ demux_t *p_demux = (demux_t *)p_this; demux_sys_t *p_sys = p_demux->p_sys; free (p_sys);}/** * Reads MIDI variable length (7, 14, 21 or 28 bits) integer. * @return read value, or -1 on EOF/error. */static int32_t ReadVarInt (stream_t *s){ uint32_t val = 0; uint8_t byte; for (unsigned i = 0; i < 4; i++) { if (stream_Read (s, &byte, 1) < 1) return -1; val = (val << 7) | (byte & 0x7f); if ((byte & 0x80) == 0) return val; } return -1;}/** * Reads (delta) time from the next event of a given track. * @param s stream to read data from (must be positioned at the right offset) */static int ReadDeltaTime (stream_t *s, mtrk_t *track){ int32_t delta_time; assert (stream_Tell (s) == track->offset); if (track->offset >= track->end) { /* This track is done */ track->next = UINT64_MAX; return 0; } delta_time = ReadVarInt (s); if (delta_time < 0) return -1; track->next += delta_time; track->offset = stream_Tell (s); return 0;}/** * Non-MIDI Meta events handler */staticint HandleMeta (demux_t *p_demux, mtrk_t *tr){ stream_t *s = p_demux->s; demux_sys_t *p_sys = p_demux->p_sys; uint8_t *payload; uint8_t type; int32_t length; int ret = 0; if (stream_Read (s, &type, 1) != 1)
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -