📄 audio_sdl.cpp
字号:
/*
* The contents of this file are subject to the Mozilla Public
* License Version 1.1 (the "License"); you may not use this file
* except in compliance with the License. You may obtain a copy of
* the License at http://www.mozilla.org/MPL/
*
* Software distributed under the License is distributed on an "AS
* IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
* implied. See the License for the specific language governing
* rights and limitations under the License.
*
* The Original Code is MPEG4IP.
*
* The Initial Developer of the Original Code is Cisco Systems Inc.
* Portions created by Cisco Systems Inc. are
* Copyright (C) Cisco Systems Inc. 2000, 2001. All Rights Reserved.
*
* Contributor(s):
* Bill May wmay@cisco.com
*/
/*
* audio.cpp provides an interface (CAudioSync) between the codec and
* the SDL audio APIs.
*/
#include <stdlib.h>
#include <string.h>
#include "player_session.h"
#include "audio_sdl.h"
#include "player_util.h"
#include <SDL_thread.h>
//#define DEBUG_SYNC 1
//#define DEBUG_AUDIO_FILL 1
//#define DEBUG_DELAY 1
#ifdef _WIN32
DEFINE_MESSAGE_MACRO(audio_message, "audiosync")
#else
#define audio_message(loglevel, fmt...) message(loglevel, "audiosync", fmt)
#endif
/*
* c routine to call into the AudioSync class callback
*/
static void c_audio_callback (void *userdata, Uint8 *stream, int len)
{
CSDLAudioSync *a = (CSDLAudioSync *)userdata;
a->audio_callback(stream, len);
}
/*
* Create an CSDLAudioSync for a session. Don't alloc any buffers until
* config is called by codec
*/
CSDLAudioSync::CSDLAudioSync (CPlayerSession *psptr, int volume) :
CAudioSync(psptr)
{
SDL_Init(SDL_INIT_AUDIO | SDL_INIT_NOPARACHUTE);
m_fill_index = m_play_index = 0;
for (int ix = 0; ix < DECODE_BUFFERS_MAX; ix++) {
m_buffer_filled[ix] = 0;
m_sample_buffer[ix] = NULL;
}
m_buffer_size = 0;
m_config_set = 0;
m_audio_initialized = 0;
m_audio_paused = 1;
m_resync_required = 1;
m_dont_fill = 0;
m_consec_no_buffers = 0;
//SDL_Init(SDL_INIT_AUDIO);
m_audio_waiting_buffer = 0;
m_audio_waiting = SDL_CreateSemaphore(0); //NULL; // will be set by decode thread
m_skipped_buffers = 0;
m_didnt_fill_buffers = 0;
m_play_time = 0 ;
m_buffer_latency = 0;
m_volume = (volume * SDL_MIX_MAXVOLUME)/100;
m_first_time = 1;
m_first_filled = 1;
m_buffer_offset_on = 0;
m_buffer_ts = 0;
m_load_audio_do_next_resync = 0;
}
/*
* Close out audio sync - stop and disconnect from SDL
*/
CSDLAudioSync::~CSDLAudioSync (void)
{
SDL_PauseAudio(1);
SDL_CloseAudio();
for (int ix = 0; ix < DECODE_BUFFERS_MAX; ix++) {
if (m_sample_buffer[ix] != NULL)
free(m_sample_buffer[ix]);
m_sample_buffer[ix] = NULL;
}
audio_message(LOG_NOTICE,
"Audio sync skipped %u buffers",
m_skipped_buffers);
audio_message(LOG_NOTICE, "didn't fill %u buffers", m_didnt_fill_buffers);
}
/*
* codec api - set up information about the stream
*/
void CSDLAudioSync::set_config (int freq,
int channels,
int format,
uint32_t sample_size)
{
if (m_config_set != 0)
return;
if (format == AUDIO_U8 || format == AUDIO_S8)
m_bytes_per_sample = 1;
else
m_bytes_per_sample = 2;
if (sample_size == 0) {
int temp;
temp = freq;
while ((temp & 0x1) == 0) temp >>= 1;
sample_size = temp;
}
m_buffer_size = channels * sample_size * m_bytes_per_sample;
for (int ix = 0; ix < DECODE_BUFFERS_MAX; ix++) {
m_buffer_filled[ix] = 0;
// I'm not sure where the 2 * comes in... Check this out
m_sample_buffer[ix] =
(uint8_t *)malloc(2 * m_buffer_size);
}
m_freq = freq;
m_channels = channels;
m_format = format;
m_config_set = 1;
m_msec_per_frame = sample_size * 1000 / m_freq;
audio_message(LOG_DEBUG, "buffer size %d msec per frame %d",
m_buffer_size, m_msec_per_frame);
};
/*
* Codec api - get_audio_buffer - will wait if there are no available
* buffers
*/
uint8_t *CSDLAudioSync::get_audio_buffer (void)
{
int ret;
int locked = 0;
if (m_dont_fill == 1) {
#ifdef DEBUG_AUDIO_FILL
audio_message(LOG_DEBUG, "first dont fill");
#endif
return (NULL);
}
if (m_audio_initialized != 0) {
locked = 1;
SDL_LockAudio();
}
ret = m_buffer_filled[m_fill_index];
if (locked)
SDL_UnlockAudio();
if (ret == 1) {
m_audio_waiting_buffer = 1;
SDL_SemWait(m_audio_waiting);
m_audio_waiting_buffer = 0;
if (m_dont_fill != 0) {
#ifdef DEBUG_AUDIO_FILL
audio_message(LOG_DEBUG, "2nd don't fill");
#endif
return (NULL);
}
locked = 0;
if (m_audio_initialized != 0) {
SDL_LockAudio();
locked = 1;
}
ret = m_buffer_filled[m_fill_index];
if (locked)
SDL_UnlockAudio();
if (ret == 1) {
#ifdef DEBUG_AUDIO_FILL
audio_message(LOG_DEBUG, "no buff");
#endif
return (NULL);
}
}
return (m_sample_buffer[m_fill_index]);
}
uint32_t CSDLAudioSync::load_audio_buffer (uint8_t *from,
uint32_t bytes,
uint64_t ts,
int resync)
{
uint8_t *to;
uint32_t copied;
int64_t diff, calc;
#ifdef DEBUG_AUDIO_FILL
audio_message(LOG_DEBUG, "fill %d bytes at "LLU", offset %d",
bytes, ts, m_buffer_offset_on);
#endif
copied = 0;
if (m_buffer_offset_on == 0) {
if (m_buffer_ts != 0 && m_buffer_ts != ts) {
m_load_audio_do_next_resync = 1;
}
m_buffer_ts = ts;
} else {
diff = ts - m_buffer_ts;
calc = m_buffer_offset_on * M_LLU;
calc /= m_bytes_per_sample;
calc /= m_freq;
if (diff > calc + 2) {
audio_message(LOG_DEBUG, "potential resync at ts "LLU" diff is "LLD" calc is "LLD,
ts, diff, calc);
uint32_t left;
left = m_buffer_size - m_buffer_offset_on;
to = get_audio_buffer();
memset(to + m_buffer_offset_on, 0, left);
filled_audio_buffer(m_buffer_ts, 0);
m_buffer_offset_on = 0;
m_load_audio_do_next_resync = 1;
m_buffer_ts = ts;
}
}
while ( bytes > 0) {
to = get_audio_buffer();
if (to == NULL) {
return copied;
}
int copy;
uint32_t left;
left = m_buffer_size - m_buffer_offset_on;
copy = MIN(left, bytes);
memcpy(to + m_buffer_offset_on,
from,
copy);
bytes -= copy;
copied += copy;
from += copy;
m_buffer_offset_on += copy;
if (m_buffer_offset_on >= m_buffer_size) {
m_buffer_offset_on = 0;
filled_audio_buffer(m_buffer_ts, resync | m_load_audio_do_next_resync);
m_buffer_ts += m_msec_per_frame;
resync = 0;
m_load_audio_do_next_resync = 0;
}
}
return (copied);
}
/*
* filled_audio_buffer - codec API - after codec fills the buffer from
* get_audio_buffer, it will call here.
*/
void CSDLAudioSync::filled_audio_buffer (uint64_t ts, int resync)
{
uint32_t fill_index;
int locked;
// m_dont_fill will be set when we have a pause
if (m_dont_fill == 1) {
return;
}
fill_index = m_fill_index;
m_fill_index++;
m_fill_index %= DECODE_BUFFERS_MAX;
locked = 0;
if (m_audio_initialized != 0) {
SDL_LockAudio();
locked = 1;
}
if (m_first_filled != 0) {
m_first_filled = 0;
} else {
int64_t diff;
diff = ts - m_last_fill_timestamp;
if (diff - m_msec_per_frame > m_msec_per_frame) {
// have a hole here - don't want to resync
#ifdef DEBUG_AUDIO_FILL
audio_message(LOG_DEBUG,
"Filling - last %llu new %llu", m_last_fill_timestamp, ts);
#endif
if (diff > ((m_msec_per_frame + 1) * 4)) {
resync = 1;
} else {
// try to fill the holes
m_last_fill_timestamp += m_msec_per_frame + 1; // fill plus extra
if (locked)
SDL_UnlockAudio();
int64_t ts_diff;
do {
uint8_t *retbuffer;
// Get and swap buffers.
retbuffer = get_audio_buffer();
if (retbuffer == NULL) {
return;
}
if (retbuffer != m_sample_buffer[m_fill_index]) {
audio_message(LOG_ERR, "retbuffer not fill index in audio sync");
return;
}
locked = 0;
if (m_audio_initialized != 0) {
SDL_LockAudio();
locked = 1;
}
m_sample_buffer[m_fill_index] = m_sample_buffer[fill_index];
m_sample_buffer[fill_index] = retbuffer;
memset(retbuffer, m_obtained.silence, m_buffer_size);
m_buffer_time[fill_index] = m_last_fill_timestamp;
m_buffer_filled[fill_index] = 1;
m_samples_loaded += m_buffer_size;
fill_index++;
fill_index %= DECODE_BUFFERS_MAX;
m_fill_index++;
m_fill_index %= DECODE_BUFFERS_MAX;
if (locked)
SDL_UnlockAudio();
audio_message(LOG_NOTICE, "Filling timestamp %llu with silence",
m_last_fill_timestamp);
m_last_fill_timestamp += m_msec_per_frame + 1; // fill plus extra
ts_diff = ts - m_last_fill_timestamp;
audio_message(LOG_DEBUG, "diff is %lld", ts_diff);
} while (ts_diff > 0);
locked = 0;
if (m_audio_initialized != 0) {
SDL_LockAudio();
locked = 1;
}
}
} else {
if (m_last_fill_timestamp == ts) {
audio_message(LOG_NOTICE, "Repeat timestamp with audio %llu", ts);
if (locked)
SDL_UnlockAudio();
return;
}
}
}
m_last_fill_timestamp = ts;
m_buffer_filled[fill_index] = 1;
m_samples_loaded += m_buffer_size;
m_buffer_time[fill_index] = ts;
if (resync) {
m_resync_required = 1;
m_resync_buffer = fill_index;
#ifdef DEBUG_AUDIO_FILL
audio_message(LOG_DEBUG, "Resync from filled_audio_buffer");
#endif
}
if (locked)
SDL_UnlockAudio();
// Check this - we might not want to do this unless we're resyncing
if (resync)
m_psptr->wake_sync_thread();
#ifdef DEBUG_AUDIO_FILL
audio_message(LOG_DEBUG, "Filling " LLU " %u %u",
ts, fill_index, m_samples_loaded);
#endif
}
void CSDLAudioSync::set_eof(void)
{
uint8_t *to;
if (m_buffer_offset_on != 0) {
to = get_audio_buffer();
if (to != NULL) {
uint32_t left;
left = m_buffer_size - m_buffer_offset_on;
memset(to + m_buffer_offset_on, 0, left);
m_buffer_offset_on = 0;
filled_audio_buffer(m_buffer_ts, 0);
m_buffer_ts += m_msec_per_frame;
}
}
CAudioSync::set_eof();
}
// Sync task api - initialize the sucker.
// May need to check out non-standard frequencies, see about conversion.
// returns 0 for not yet, 1 for initialized, -1 for error
int CSDLAudioSync::initialize_audio (int have_video)
{
if (m_audio_initialized == 0) {
if (m_config_set) {
SDL_AudioSpec wanted;
m_do_sync = have_video;
wanted.freq = m_freq;
wanted.channels = m_channels;
wanted.format = m_format;
int sample_size;
sample_size = m_buffer_size / (m_channels * m_bytes_per_sample);
#ifndef _WINDOWS
uint32_t ix;
for (ix = 2; ix <= 0x8000; ix <<= 1) {
if ((sample_size & ~(ix - 1)) == 0) {
break;
}
}
ix >>= 1;
audio_message(LOG_DEBUG, "Sample size is %d", ix);
m_sample_size = ix;
#else
m_sample_size = 4096;
#endif
if ((m_do_sync == 0) && m_sample_size < 4096)
m_sample_size = 4096;
wanted.samples = m_sample_size;
wanted.callback = c_audio_callback;
wanted.userdata = this;
#if 1
audio_message(LOG_INFO,
"requested f %d chan %d format %x samples %d",
wanted.freq,
wanted.channels,
wanted.format,
wanted.samples);
#endif
int ret = SDL_OpenAudio(&wanted, &m_obtained);
if (ret < 0) {
audio_message(LOG_CRIT, "Couldn't open audio, %s", SDL_GetError());
return (-1);
}
#if 1
audio_message(LOG_INFO, "got f %d chan %d format %x samples %d size %u",
m_obtained.freq,
m_obtained.channels,
m_obtained.format,
m_obtained.samples,
m_obtained.size);
#endif
m_audio_initialized = 1;
m_use_SDL_delay = SDL_HasAudioDelayMsec();
if (m_use_SDL_delay)
audio_message(LOG_NOTICE, "Using delay measurement from SDL");
} else {
return 0; // check again pretty soon...
}
}
return (1);
}
// This is used by the sync thread to determine if a valid amount of
⌨️ 快捷键说明
复制代码
Ctrl + C
搜索代码
Ctrl + F
全屏模式
F11
切换主题
Ctrl + Shift + D
显示快捷键
?
增大字号
Ctrl + =
减小字号
Ctrl + -