decoder/vorbis: reimplement using class OggDecoder
Use libvorbis instead of libvorbisfile, which gives us more control over the decoding process.
This commit is contained in:
parent
24fa3f5e7b
commit
2ee43c403c
@ -964,7 +964,7 @@ if test x$enable_tremor = xyes; then
|
|||||||
fi
|
fi
|
||||||
fi
|
fi
|
||||||
|
|
||||||
MPD_AUTO_PKG(vorbis, VORBIS, [vorbisfile vorbis ogg],
|
MPD_AUTO_PKG(vorbis, VORBIS, [vorbis ogg],
|
||||||
[Ogg Vorbis decoder], [libvorbis not found])
|
[Ogg Vorbis decoder], [libvorbis not found])
|
||||||
if test x$enable_vorbis = xyes; then
|
if test x$enable_vorbis = xyes; then
|
||||||
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support])
|
AC_DEFINE(ENABLE_VORBIS_DECODER, 1, [Define for Ogg Vorbis support])
|
||||||
|
@ -19,9 +19,8 @@
|
|||||||
|
|
||||||
#include "config.h"
|
#include "config.h"
|
||||||
#include "VorbisDecoderPlugin.h"
|
#include "VorbisDecoderPlugin.h"
|
||||||
|
#include "OggDecoder.hxx"
|
||||||
#include "lib/xiph/VorbisComments.hxx"
|
#include "lib/xiph/VorbisComments.hxx"
|
||||||
#include "lib/xiph/OggSyncState.hxx"
|
|
||||||
#include "lib/xiph/OggStreamState.hxx"
|
|
||||||
#include "lib/xiph/OggPacket.hxx"
|
#include "lib/xiph/OggPacket.hxx"
|
||||||
#include "lib/xiph/OggFind.hxx"
|
#include "lib/xiph/OggFind.hxx"
|
||||||
#include "VorbisDomain.hxx"
|
#include "VorbisDomain.hxx"
|
||||||
@ -38,139 +37,116 @@
|
|||||||
#include "Log.hxx"
|
#include "Log.hxx"
|
||||||
|
|
||||||
#ifndef HAVE_TREMOR
|
#ifndef HAVE_TREMOR
|
||||||
#define OV_EXCLUDE_STATIC_CALLBACKS
|
#include <vorbis/codec.h>
|
||||||
#include <vorbis/vorbisfile.h>
|
|
||||||
#else
|
#else
|
||||||
#include <tremor/ivorbisfile.h>
|
#include <tremor/ivorbiscodec.h>
|
||||||
/* Macros to make Tremor's API look like libogg. Tremor always
|
|
||||||
returns host-byte-order 16-bit signed data, and uses integer
|
|
||||||
milliseconds where libogg uses double seconds.
|
|
||||||
*/
|
|
||||||
#define ov_read(VF, BUFFER, LENGTH, BIGENDIANP, WORD, SGNED, BITSTREAM) \
|
|
||||||
ov_read(VF, BUFFER, LENGTH, BITSTREAM)
|
|
||||||
#define ov_time_total(VF, I) ((double)ov_time_total(VF, I)/1000)
|
|
||||||
#define ov_time_tell(VF) ((double)ov_time_tell(VF)/1000)
|
|
||||||
#define ov_time_seek_page(VF, S) (ov_time_seek_page(VF, (S)*1000))
|
|
||||||
#endif /* HAVE_TREMOR */
|
#endif /* HAVE_TREMOR */
|
||||||
|
|
||||||
#include <errno.h>
|
#include <stdexcept>
|
||||||
|
|
||||||
struct VorbisInputStream {
|
class VorbisDecoder final : public OggDecoder {
|
||||||
Decoder *const decoder;
|
#ifdef HAVE_TREMOR
|
||||||
|
static constexpr SampleFormat sample_format = SampleFormat::S16;
|
||||||
|
typedef ogg_int32_t in_sample_t;
|
||||||
|
typedef int16_t out_sample_t;
|
||||||
|
#else
|
||||||
|
static constexpr SampleFormat sample_format = SampleFormat::FLOAT;
|
||||||
|
typedef float in_sample_t;
|
||||||
|
typedef float out_sample_t;
|
||||||
|
#endif
|
||||||
|
|
||||||
InputStream &input_stream;
|
unsigned remaining_header_packets;
|
||||||
bool seekable;
|
|
||||||
|
|
||||||
VorbisInputStream(Decoder *_decoder, InputStream &_is)
|
vorbis_info vi;
|
||||||
:decoder(_decoder), input_stream(_is),
|
vorbis_comment vc;
|
||||||
seekable(input_stream.CheapSeeking()) {}
|
vorbis_dsp_state dsp;
|
||||||
};
|
vorbis_block block;
|
||||||
|
|
||||||
static size_t ogg_read_cb(void *ptr, size_t size, size_t nmemb, void *data)
|
/**
|
||||||
{
|
* If non-zero, then a previous Vorbis stream has been found
|
||||||
VorbisInputStream *vis = (VorbisInputStream *)data;
|
* already with this number of channels.
|
||||||
size_t ret = decoder_read(vis->decoder, vis->input_stream,
|
*/
|
||||||
ptr, size * nmemb);
|
AudioFormat audio_format = AudioFormat::Undefined();
|
||||||
|
size_t frame_size;
|
||||||
|
|
||||||
errno = 0;
|
bool dsp_initialized = false;
|
||||||
|
|
||||||
return ret / size;
|
public:
|
||||||
}
|
explicit VorbisDecoder(DecoderReader &reader)
|
||||||
|
:OggDecoder(reader) {
|
||||||
static int ogg_seek_cb(void *data, ogg_int64_t _offset, int whence)
|
InitVorbis();
|
||||||
{
|
|
||||||
VorbisInputStream *vis = (VorbisInputStream *)data;
|
|
||||||
InputStream &is = vis->input_stream;
|
|
||||||
|
|
||||||
if (!vis->seekable ||
|
|
||||||
(vis->decoder != nullptr &&
|
|
||||||
decoder_get_command(*vis->decoder) == DecoderCommand::STOP))
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
offset_type offset = _offset;
|
|
||||||
switch (whence) {
|
|
||||||
case SEEK_SET:
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SEEK_CUR:
|
|
||||||
offset += is.GetOffset();
|
|
||||||
break;
|
|
||||||
|
|
||||||
case SEEK_END:
|
|
||||||
if (!is.KnownSize())
|
|
||||||
return -1;
|
|
||||||
|
|
||||||
offset += is.GetSize();
|
|
||||||
break;
|
|
||||||
|
|
||||||
default:
|
|
||||||
return -1;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
return is.LockSeek(offset, IgnoreError())
|
~VorbisDecoder() {
|
||||||
? 0 : -1;
|
DeinitVorbis();
|
||||||
}
|
}
|
||||||
|
|
||||||
/* TODO: check Ogg libraries API and see if we can just not have this func */
|
bool Seek(uint64_t where_frame);
|
||||||
static int ogg_close_cb(gcc_unused void *data)
|
|
||||||
{
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
static long ogg_tell_cb(void *data)
|
private:
|
||||||
{
|
void InitVorbis() {
|
||||||
VorbisInputStream *vis = (VorbisInputStream *)data;
|
vorbis_info_init(&vi);
|
||||||
|
vorbis_comment_init(&vc);
|
||||||
|
}
|
||||||
|
|
||||||
return (long)vis->input_stream.GetOffset();
|
void DeinitVorbis() {
|
||||||
}
|
if (dsp_initialized) {
|
||||||
|
dsp_initialized = false;
|
||||||
|
|
||||||
static const ov_callbacks vorbis_is_callbacks = {
|
vorbis_block_clear(&block);
|
||||||
ogg_read_cb,
|
vorbis_dsp_clear(&dsp);
|
||||||
ogg_seek_cb,
|
}
|
||||||
ogg_close_cb,
|
|
||||||
ogg_tell_cb,
|
vorbis_comment_clear(&vc);
|
||||||
|
vorbis_info_clear(&vi);
|
||||||
|
}
|
||||||
|
|
||||||
|
void ReinitVorbis() {
|
||||||
|
DeinitVorbis();
|
||||||
|
InitVorbis();
|
||||||
|
}
|
||||||
|
|
||||||
|
void SubmitInit();
|
||||||
|
bool SubmitSomePcm();
|
||||||
|
void SubmitPcm();
|
||||||
|
|
||||||
|
protected:
|
||||||
|
/* virtual methods from class OggVisitor */
|
||||||
|
void OnOggBeginning(const ogg_packet &packet) override;
|
||||||
|
void OnOggPacket(const ogg_packet &packet) override;
|
||||||
|
void OnOggEnd() override;
|
||||||
};
|
};
|
||||||
|
|
||||||
static const char *
|
bool
|
||||||
vorbis_strerror(int code)
|
VorbisDecoder::Seek(uint64_t where_frame)
|
||||||
{
|
{
|
||||||
switch (code) {
|
assert(IsSeekable());
|
||||||
case OV_EREAD:
|
assert(input_stream.IsSeekable());
|
||||||
return "read error";
|
assert(input_stream.KnownSize());
|
||||||
|
|
||||||
case OV_ENOTVORBIS:
|
const ogg_int64_t where_granulepos(where_frame);
|
||||||
return "not vorbis stream";
|
|
||||||
|
|
||||||
case OV_EVERSION:
|
if (!SeekGranulePos(where_granulepos, IgnoreError()))
|
||||||
return "vorbis version mismatch";
|
|
||||||
|
|
||||||
case OV_EBADHEADER:
|
|
||||||
return "invalid vorbis header";
|
|
||||||
|
|
||||||
case OV_EFAULT:
|
|
||||||
return "internal logic error";
|
|
||||||
|
|
||||||
default:
|
|
||||||
return "unknown error";
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
static bool
|
|
||||||
vorbis_is_open(VorbisInputStream *vis, OggVorbis_File *vf)
|
|
||||||
{
|
|
||||||
int ret = ov_open_callbacks(vis, vf, nullptr, 0, vorbis_is_callbacks);
|
|
||||||
if (ret < 0) {
|
|
||||||
if (vis->decoder == nullptr ||
|
|
||||||
decoder_get_command(*vis->decoder) == DecoderCommand::NONE)
|
|
||||||
FormatWarning(vorbis_domain,
|
|
||||||
"Failed to open Ogg Vorbis stream: %s",
|
|
||||||
vorbis_strerror(ret));
|
|
||||||
return false;
|
return false;
|
||||||
}
|
|
||||||
|
|
||||||
|
vorbis_synthesis_restart(&dsp);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
VorbisDecoder::OnOggBeginning(const ogg_packet &_packet)
|
||||||
|
{
|
||||||
|
/* libvorbis wants non-const packets */
|
||||||
|
ogg_packet &packet = const_cast<ogg_packet &>(_packet);
|
||||||
|
|
||||||
|
ReinitVorbis();
|
||||||
|
|
||||||
|
if (vorbis_synthesis_headerin(&vi, &vc, &packet) != 0)
|
||||||
|
throw std::runtime_error("Unrecognized Vorbis BOS packet");
|
||||||
|
|
||||||
|
remaining_header_packets = 2;
|
||||||
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
vorbis_send_comments(Decoder &decoder, InputStream &is,
|
vorbis_send_comments(Decoder &decoder, InputStream &is,
|
||||||
char **comments)
|
char **comments)
|
||||||
@ -183,16 +159,138 @@ vorbis_send_comments(Decoder &decoder, InputStream &is,
|
|||||||
delete tag;
|
delete tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
#ifndef HAVE_TREMOR
|
void
|
||||||
static void
|
VorbisDecoder::SubmitInit()
|
||||||
vorbis_interleave(float *dest, const float *const*src,
|
|
||||||
unsigned nframes, unsigned channels)
|
|
||||||
{
|
{
|
||||||
PcmInterleaveFloat(dest, ConstBuffer<const float *>(src, channels),
|
assert(!dsp_initialized);
|
||||||
nframes);
|
|
||||||
|
Error error;
|
||||||
|
if (!audio_format_init_checked(audio_format, vi.rate, sample_format,
|
||||||
|
vi.channels, error))
|
||||||
|
throw std::runtime_error(error.GetMessage());
|
||||||
|
|
||||||
|
frame_size = audio_format.GetFrameSize();
|
||||||
|
|
||||||
|
const auto eos_granulepos = UpdateEndGranulePos();
|
||||||
|
const auto duration = eos_granulepos >= 0
|
||||||
|
? SignedSongTime::FromScale<uint64_t>(eos_granulepos,
|
||||||
|
audio_format.sample_rate)
|
||||||
|
: SignedSongTime::Negative();
|
||||||
|
|
||||||
|
decoder_initialized(decoder, audio_format,
|
||||||
|
eos_granulepos > 0, duration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool
|
||||||
|
VorbisDecoder::SubmitSomePcm()
|
||||||
|
{
|
||||||
|
in_sample_t **pcm;
|
||||||
|
int result = vorbis_synthesis_pcmout(&dsp, &pcm);
|
||||||
|
if (result <= 0)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
out_sample_t buffer[4096];
|
||||||
|
const unsigned channels = audio_format.channels;
|
||||||
|
size_t max_frames = ARRAY_SIZE(buffer) / channels;
|
||||||
|
size_t n_frames = std::min(size_t(result), max_frames);
|
||||||
|
|
||||||
|
#ifdef HAVE_TREMOR
|
||||||
|
for (unsigned c = 0; c < channels; ++c) {
|
||||||
|
const auto *src = pcm[c];
|
||||||
|
auto *dest = &buffer[c];
|
||||||
|
|
||||||
|
for (size_t i = 0; i < n_frames; ++i) {
|
||||||
|
*dest = *src++;
|
||||||
|
dest += channels;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
PcmInterleaveFloat(buffer,
|
||||||
|
ConstBuffer<const in_sample_t *>(pcm,
|
||||||
|
channels),
|
||||||
|
n_frames);
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
vorbis_synthesis_read(&dsp, n_frames);
|
||||||
|
|
||||||
|
const size_t nbytes = n_frames * frame_size;
|
||||||
|
auto cmd = decoder_data(decoder, input_stream,
|
||||||
|
buffer, nbytes,
|
||||||
|
0);
|
||||||
|
if (cmd != DecoderCommand::NONE)
|
||||||
|
throw cmd;
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
VorbisDecoder::SubmitPcm()
|
||||||
|
{
|
||||||
|
while (SubmitSomePcm()) {}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
VorbisDecoder::OnOggPacket(const ogg_packet &_packet)
|
||||||
|
{
|
||||||
|
/* libvorbis wants non-const packets */
|
||||||
|
ogg_packet &packet = const_cast<ogg_packet &>(_packet);
|
||||||
|
|
||||||
|
if (remaining_header_packets > 0) {
|
||||||
|
if (vorbis_synthesis_headerin(&vi, &vc, &packet) != 0)
|
||||||
|
throw std::runtime_error("Unrecognized Vorbis header packet");
|
||||||
|
|
||||||
|
if (--remaining_header_packets > 0)
|
||||||
|
return;
|
||||||
|
|
||||||
|
if (audio_format.IsDefined()) {
|
||||||
|
/* TODO: change the MPD decoder plugin API to
|
||||||
|
allow mid-song AudioFormat changes */
|
||||||
|
if ((unsigned)vi.rate != audio_format.sample_rate ||
|
||||||
|
(unsigned)vi.channels != audio_format.channels)
|
||||||
|
throw std::runtime_error("Next stream has different audio format");
|
||||||
|
} else
|
||||||
|
SubmitInit();
|
||||||
|
|
||||||
|
vorbis_send_comments(decoder, input_stream, vc.user_comments);
|
||||||
|
|
||||||
|
ReplayGainInfo rgi;
|
||||||
|
if (vorbis_comments_to_replay_gain(rgi, vc.user_comments))
|
||||||
|
decoder_replay_gain(decoder, &rgi);
|
||||||
|
} else {
|
||||||
|
if (!dsp_initialized) {
|
||||||
|
dsp_initialized = true;
|
||||||
|
|
||||||
|
vorbis_synthesis_init(&dsp, &vi);
|
||||||
|
vorbis_block_init(&dsp, &block);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vorbis_synthesis(&block, &packet) != 0) {
|
||||||
|
/* ignore bad packets, but give the MPD core a
|
||||||
|
chance to stop us */
|
||||||
|
auto cmd = decoder_get_command(decoder);
|
||||||
|
if (cmd != DecoderCommand::NONE)
|
||||||
|
throw cmd;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vorbis_synthesis_blockin(&dsp, &block) != 0)
|
||||||
|
throw std::runtime_error("vorbis_synthesis_blockin() failed");
|
||||||
|
|
||||||
|
SubmitPcm();
|
||||||
|
|
||||||
|
#ifndef HAVE_TREMOR
|
||||||
|
if (packet.granulepos > 0)
|
||||||
|
decoder_timestamp(decoder,
|
||||||
|
vorbis_granule_time(&dsp, packet.granulepos));
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void
|
||||||
|
VorbisDecoder::OnOggEnd()
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
/* public */
|
/* public */
|
||||||
|
|
||||||
static bool
|
static bool
|
||||||
@ -204,16 +302,6 @@ vorbis_init(gcc_unused const ConfigBlock &block)
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
gcc_pure
|
|
||||||
static SignedSongTime
|
|
||||||
vorbis_duration(OggVorbis_File &vf)
|
|
||||||
{
|
|
||||||
auto total = ov_time_total(&vf, -1);
|
|
||||||
return total >= 0
|
|
||||||
? SignedSongTime::FromS(total)
|
|
||||||
: SignedSongTime::Negative();
|
|
||||||
}
|
|
||||||
|
|
||||||
static void
|
static void
|
||||||
vorbis_stream_decode(Decoder &decoder,
|
vorbis_stream_decode(Decoder &decoder,
|
||||||
InputStream &input_stream)
|
InputStream &input_stream)
|
||||||
@ -225,118 +313,23 @@ vorbis_stream_decode(Decoder &decoder,
|
|||||||
moved it */
|
moved it */
|
||||||
input_stream.LockRewind(IgnoreError());
|
input_stream.LockRewind(IgnoreError());
|
||||||
|
|
||||||
VorbisInputStream vis(&decoder, input_stream);
|
DecoderReader reader(decoder, input_stream);
|
||||||
OggVorbis_File vf;
|
VorbisDecoder d(reader);
|
||||||
if (!vorbis_is_open(&vis, &vf))
|
|
||||||
return;
|
|
||||||
|
|
||||||
const vorbis_info *vi = ov_info(&vf, -1);
|
while (true) {
|
||||||
if (vi == nullptr) {
|
try {
|
||||||
LogWarning(vorbis_domain, "ov_info() has failed");
|
d.Visit();
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
Error error;
|
|
||||||
AudioFormat audio_format;
|
|
||||||
if (!audio_format_init_checked(audio_format, vi->rate,
|
|
||||||
#ifdef HAVE_TREMOR
|
|
||||||
SampleFormat::S16,
|
|
||||||
#else
|
|
||||||
SampleFormat::FLOAT,
|
|
||||||
#endif
|
|
||||||
vi->channels, error)) {
|
|
||||||
LogError(error);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
decoder_initialized(decoder, audio_format, vis.seekable,
|
|
||||||
vorbis_duration(vf));
|
|
||||||
|
|
||||||
#ifdef HAVE_TREMOR
|
|
||||||
char buffer[4096];
|
|
||||||
#else
|
|
||||||
float buffer[2048];
|
|
||||||
const int frames_per_buffer =
|
|
||||||
ARRAY_SIZE(buffer) / audio_format.channels;
|
|
||||||
const unsigned frame_size = sizeof(buffer[0]) * audio_format.channels;
|
|
||||||
#endif
|
|
||||||
|
|
||||||
int prev_section = -1;
|
|
||||||
unsigned kbit_rate = 0;
|
|
||||||
|
|
||||||
DecoderCommand cmd = decoder_get_command(decoder);
|
|
||||||
while (cmd != DecoderCommand::STOP) {
|
|
||||||
if (cmd == DecoderCommand::SEEK) {
|
|
||||||
auto seek_where = decoder_seek_where_frame(decoder);
|
|
||||||
if (0 == ov_pcm_seek_page(&vf, seek_where)) {
|
|
||||||
decoder_command_finished(decoder);
|
|
||||||
} else
|
|
||||||
decoder_seek_error(decoder);
|
|
||||||
}
|
|
||||||
|
|
||||||
int current_section;
|
|
||||||
|
|
||||||
#ifdef HAVE_TREMOR
|
|
||||||
long nbytes = ov_read(&vf, buffer, sizeof(buffer),
|
|
||||||
IsBigEndian(), 2, 1,
|
|
||||||
¤t_section);
|
|
||||||
#else
|
|
||||||
float **per_channel;
|
|
||||||
long nframes = ov_read_float(&vf, &per_channel,
|
|
||||||
frames_per_buffer,
|
|
||||||
¤t_section);
|
|
||||||
long nbytes = nframes;
|
|
||||||
if (nframes > 0) {
|
|
||||||
vorbis_interleave(buffer,
|
|
||||||
(const float*const*)per_channel,
|
|
||||||
nframes, audio_format.channels);
|
|
||||||
nbytes *= frame_size;
|
|
||||||
}
|
|
||||||
#endif
|
|
||||||
|
|
||||||
if (nbytes == OV_HOLE) /* bad packet */
|
|
||||||
nbytes = 0;
|
|
||||||
else if (nbytes <= 0)
|
|
||||||
/* break on EOF or other error */
|
|
||||||
break;
|
break;
|
||||||
|
} catch (DecoderCommand cmd) {
|
||||||
if (current_section != prev_section) {
|
if (cmd == DecoderCommand::SEEK) {
|
||||||
vi = ov_info(&vf, -1);
|
if (d.Seek(decoder_seek_where_frame(decoder)))
|
||||||
if (vi == nullptr) {
|
decoder_command_finished(decoder);
|
||||||
LogWarning(vorbis_domain,
|
else
|
||||||
"ov_info() has failed");
|
decoder_seek_error(decoder);
|
||||||
|
} else if (cmd != DecoderCommand::NONE)
|
||||||
break;
|
break;
|
||||||
}
|
|
||||||
|
|
||||||
if (vi->rate != (long)audio_format.sample_rate ||
|
|
||||||
vi->channels != (int)audio_format.channels) {
|
|
||||||
/* we don't support audio format
|
|
||||||
change yet */
|
|
||||||
LogWarning(vorbis_domain,
|
|
||||||
"audio format change, stopping here");
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
|
|
||||||
char **comments = ov_comment(&vf, -1)->user_comments;
|
|
||||||
vorbis_send_comments(decoder, input_stream, comments);
|
|
||||||
|
|
||||||
ReplayGainInfo rgi;
|
|
||||||
if (vorbis_comments_to_replay_gain(rgi, comments))
|
|
||||||
decoder_replay_gain(decoder, &rgi);
|
|
||||||
|
|
||||||
prev_section = current_section;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
long test = ov_bitrate_instant(&vf);
|
|
||||||
if (test > 0)
|
|
||||||
kbit_rate = test / 1000;
|
|
||||||
|
|
||||||
cmd = decoder_data(decoder, input_stream,
|
|
||||||
buffer, nbytes,
|
|
||||||
kbit_rate);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
ov_clear(&vf);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
static void
|
static void
|
||||||
|
Loading…
Reference in New Issue
Block a user