C++中stringsteam中的str(), clear(), eof(), rdbuf->in_avail()的用法,以及多次使用stringsteam的注意事项

本文介绍了C++中stringstream在使用过程中遇到的问题,特别是关于str(), clear(), eof(), rdbuf()->in_avail()的用法。当stringstream输出完缓存内容后,会设置结束状态,导致无法正常工作。通过调用clear()可以清除状态,使其恢复正常。同时提醒开发者,判断stringstream是否为空不应仅依赖于empty()方法,因为它可能返回true但仍有内容可读。" 108708155,5879680,Windows访问Linux分区的3种工具详解,"['Linux', 'Windows', '运维', '文件系统']

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今儿使用stringsteam可是遇到了一个大坑了

先把坑贴上来

int main()
{
    stringstream ss;
    int tmp = 0;
    ss << "1";
    ss >> tmp;
    cout << tmp << endl;
    ss << "2";
    ss >> tmp;
    cout << tmp << endl;
};

大家觉得这两次的输出是什么,1和2? 哈哈哈 我就是这么错的,它会输出1和1

为啥捏,这解释的东西就比较多了,首先单纯的解释这个问题,我看网上并没有解释相关问题

首先,我们都知道ss.clear()是用来清楚ss的状态,问题就在于,当ss将缓存中的所有东西输出之后,就会设置一个结束的状态,从而导致ss无法正常使用了,看代码

int main()
{
    stringstream ss;
    int tmp = 0;
    ss << "1";
    ss >> tmp;
    cout << "第一次输出tmp " << tmp << endl;
    cout << "ss 为空吗? " << ss.eof() << endl;
    ss << "2";
    cout << "ss此时的缓存大小是  " << ss.rdbuf()->in_avail() << endl;
    ss >> tmp;
    cout << tmp << endl;
};

输出结果

可以看到,当我们第一次输出tmp之后,ss标志位eof已经是空的了,所以当再次ss << "2"的时候,并没有写入到缓存中,所以此时ss可用的缓存就是0

因此,如果我们想继续接着这样使用的话,就应该先把ss的状态清空一下

int main()
{
    stringstream ss;
    int tmp = 0;
    ss << "1";
    ss >> tmp;
    cout << "第一次输出tmp " << tmp << endl;
    cout << "ss 为空吗? " << ss.eof() << endl;
    //清空ss状态
    ss.clear();
    cout << "此时ss的eof是" << ss.eof() << endl;
    ss << "2";
    cout << "ss此时的缓存大小是  " << ss.rdbuf()->in_avail() << endl;
    ss >> tmp;
    cout << tmp << endl;
};

结果

可以看到此时ss可以按照我们设想的方式工作了。

切记, 判断ss是否为空的方式是 ? 明天修改

if (ss.rdbuf() -> in_avail() == 0)
和
if (ss.str().empty())

万万不是

if(ss.str().size() == 0)

因此ss占用的内存并不会释放,甚至是可读的,但就是empty()为真。

./audio_stream 禁用重采样 使用URL: https://ting8.yymp3.com/new27/liyugang6/6.mp3 正在下载音频文件... 等待音频数据... 等待缓冲: 2.0秒 (当前: 0.0秒) 缓冲中: 0.0秒/2.0秒 (0.0%) 文件大小: 3.05 MB 缓冲中: 1.1秒/2.0秒 (52.8%) 缓冲完成: 2.3秒/2.0秒 检测到新音频格式: 44100 Hz, 2 声道 播放格式: 44100 Hz, 2 声道 (原始: 44100 Hz) [重采样禁用] 缓冲: 186.6s | 速度: 1.020 | 队列: 2915.2kb 下载完成, 总大小: 3.05 MB 缓冲: 0.7s | 速度: 1.015 | 队列: 10.8kbbbbb 缓冲不足 (0.7秒 < 1.0秒), 重新缓冲... 等待缓冲: 2.0秒 (当前: 0.7秒) 下载完成,剩余数据: 0.7秒 缓冲恢复,继续播放 缓冲不足 (0.7秒 < 1.0秒), 重新缓冲... 等待缓冲: 2.0秒 (当前: 0.7秒) 下载完成,剩余数据: 0.7秒 缓冲恢复,继续播放 缓冲: 0.0s | 速度: 1.008 | 队列: 0.0kb 缓冲不足 (0.0秒 < 1.0秒), 重新缓冲... 等待缓冲: 2.0秒 (当前: 0.0秒) 下载完成,剩余数据: 0.0秒 缓冲恢复,继续播放 播放线程退出 播放结束 MP3文件而不是流媒体,已经播放完毕就不需要再去缓冲了,直接停止播放就行了,只有播放持续不断的流媒体才会尝试去缓冲,再优化一下 #include <stdio.h> #include <stdlib.h> #include <string.h> #include <pthread.h> #include <curl/curl.h> #include <mpg123.h> #include <pulse/simple.h> #include <pulse/error.h> #include <unistd.h> #include <samplerate.h> #include <time.h> #include <math.h> #include <malloc.h> #include <errno.h> #define BUFFER_SIZE 8192 #define USE_RESAMPLING 0 #define INITIAL_BUFFER_SECONDS 2.0 #define MIN_BUFFER_SECONDS 1.0 #define TARGET_BUFFER_SECONDS 2.0 #define BYTES_PER_SECOND (128 * 1000 / 8) // 共享数据结构 typedef struct { mpg123_handle *mh; pa_simple *pa_stream; pthread_mutex_t mutex; int stop; int format_initialized; int channels; long rate; int buffering; size_t buffer_bytes; double buffer_level; double playback_speed; #if USE_RESAMPLING SRC_STATE *resampler; float *resample_in_buf; float *resample_out_buf; size_t resample_in_size; size_t resample_out_size; long target_rate; #endif } PlayerState; // 自定义结构体用于存储音频数据块 struct data_chunk { unsigned char *data; size_t size; struct data_chunk *next; }; // 线程安全队列 struct data_queue { struct data_chunk *head; struct data_chunk *tail; pthread_mutex_t mutex; pthread_cond_t cond; pthread_cond_t buffer_cond; size_t total_size; size_t target_size; int finished; }; // 下载状态结构 struct DownloadStatus { size_t total_size; // 文件总大小 size_t downloaded_size; // 已下载大小 double last_update; // 最后更新时间 int display_active; // 是否显示进度 }; // 初始化队列 void queue_init(struct data_queue *q) { q->head = q->tail = NULL; pthread_mutex_init(&q->mutex, NULL); pthread_cond_init(&q->cond, NULL); pthread_cond_init(&q->buffer_cond, NULL); q->total_size = 0; q->target_size = 0; q->finished = 0; } // 添加数据到队列 void queue_push(struct data_queue *q, unsigned char *data, size_t size) { struct data_chunk *chunk = malloc(sizeof(struct data_chunk)); if (!chunk) return; chunk->data = malloc(size); if (!chunk->data) { free(chunk); return; } memcpy(chunk->data, data, size); chunk->size = size; chunk->next = NULL; pthread_mutex_lock(&q->mutex); if (q->tail) { q->tail->next = chunk; } else { q->head = chunk; } q->tail = chunk; q->total_size += size; if (q->target_size > 0 && q->total_size >= q->target_size) { pthread_cond_signal(&q->buffer_cond); } pthread_cond_signal(&q->cond); pthread_mutex_unlock(&q->mutex); } // 标记下载完成 void queue_finish(struct data_queue *q) { pthread_mutex_lock(&q->mutex); q->finished = 1; pthread_cond_signal(&q->cond); pthread_cond_signal(&q->buffer_cond); pthread_mutex_unlock(&q->mutex); } // 从队列获取数据 struct data_chunk *queue_pop(struct data_queue *q) { pthread_mutex_lock(&q->mutex); // 等待直到有数据或下载完成 while (!q->head && !q->finished) { pthread_cond_wait(&q->cond, &q->mutex); } // 如果队列为空且下载完成,返回NULL if (!q->head) { pthread_mutex_unlock(&q->mutex); return NULL; } struct data_chunk *chunk = q->head; q->head = chunk->next; if (!q->head) q->tail = NULL; q->total_size -= chunk->size; pthread_mutex_unlock(&q->mutex); return chunk; } // 清理队列 void queue_cleanup(struct data_queue *q) { pthread_mutex_lock(&q->mutex); struct data_chunk *chunk = q->head; while (chunk) { struct data_chunk *next = chunk->next; free(chunk->data); free(chunk); chunk = next; } q->head = q->tail = NULL; q->total_size = 0; pthread_mutex_unlock(&q->mutex); } // 设置缓冲目标并等待 void set_buffer_target(struct data_queue *q, struct DownloadStatus *status, size_t target_size) { pthread_mutex_lock(&q->mutex); q->target_size = target_size; // 暂停下载进度显示 if (status) status->display_active = 0; if (q->total_size >= target_size) { pthread_mutex_unlock(&q->mutex); if (status) status->display_active = 1; return; } printf("\n等待缓冲: %.1f秒 (当前: %.1f秒)\n", (double)target_size / BYTES_PER_SECOND, (double)q->total_size / BYTES_PER_SECOND); while (q->total_size < target_size && !q->finished) { struct timespec ts; clock_gettime(CLOCK_REALTIME, &ts); ts.tv_sec += 1; printf("缓冲中: %.1f秒/%.1f秒 (%.1f%%)\r", (double)q->total_size / BYTES_PER_SECOND, (double)target_size / BYTES_PER_SECOND, (double)q->total_size / target_size * 100); fflush(stdout); pthread_cond_timedwait(&q->buffer_cond, &q->mutex, &ts); } if (q->finished) { printf("\n下载完成,剩余数据: %.1f秒\n", (double)q->total_size / BYTES_PER_SECOND); } else { printf("\n缓冲完成: %.1f秒/%.1f秒\n", (double)q->total_size / BYTES_PER_SECOND, (double)target_size / BYTES_PER_SECOND); } // 恢复下载进度显示 if (status) status->display_active = 1; pthread_mutex_unlock(&q->mutex); } // libcurl回调函数 static size_t write_callback(void *contents, size_t size, size_t nmemb, void *userp) { size_t realsize = size * nmemb; struct data_queue *q = (struct data_queue *)userp; queue_push(q, (unsigned char *)contents, realsize); return realsize; } // 添加抖动处理函数 void apply_dither(short *output, float *input, size_t samples) { const float scale = 32767.0f; for (size_t i = 0; i < samples; i++) { float sample = input[i]; // 简单的抖动处理 float dither_val = (rand() / (float)RAND_MAX) * 0.0001f; sample += dither_val; // 软裁剪 if (sample > 1.0f) sample = 1.0f; if (sample < -1.0f) sample = -1.0f; output[i] = (short)(sample * scale); } } // 简单的低通滤波器 void apply_lowpass(float *buffer, size_t samples, int channels) { static float prev[8] = {0}; const float alpha = 0.15f; for (size_t i = 0; i < samples; i++) { int ch = i % channels; float current = buffer[i]; buffer[i] = alpha * current + (1 - alpha) * prev[ch]; prev[ch] = buffer[i]; } } // 初始化PulseAudio设备 int init_pulse_audio(PlayerState *state, long rate, int channels) { if (state->format_initialized && state->pa_stream) { if (state->rate == rate && state->channels == channels) { return 1; } pa_simple_free(state->pa_stream); state->pa_stream = NULL; } pa_sample_spec ss; #if USE_RESAMPLING ss.rate = state->target_rate; #else ss.rate = rate; #endif ss.format = PA_SAMPLE_S16LE; ss.channels = channels; pa_buffer_attr buffer_attr = { .maxlength = (uint32_t)-1, .tlength = (uint32_t)-1, .prebuf = (uint32_t)-1, .minreq = (uint32_t)4096, .fragsize = (uint32_t)1024 }; int error; state->pa_stream = pa_simple_new( NULL, "AudioStream", PA_STREAM_PLAYBACK, NULL, "Music", &ss, NULL, &buffer_attr, &error); if (!state->pa_stream) { fprintf(stderr, "无法创建PulseAudio连接: %s\n", pa_strerror(error)); return 0; } state->rate = rate; state->channels = channels; state->format_initialized = 1; state->playback_speed = 1.0; printf("播放格式: %d Hz, %d 声道 (原始: %ld Hz) %s\n", ss.rate, channels, rate, #if USE_RESAMPLING "[重采样启用]" #else "[重采样禁用]" #endif ); #if USE_RESAMPLING if (state->resampler) { src_delete(state->resampler); state->resampler = NULL; } int src_error; state->resampler = src_new(SRC_SINC_FASTEST, channels, &src_error); if (!state->resampler) { fprintf(stderr, "无法创建重采样器: %s\n", src_strerror(src_error)); return 0; } double src_ratio = (double)state->target_rate / rate; src_set_ratio(state->resampler, src_ratio); size_t needed_in_size = BUFFER_SIZE * sizeof(float) * channels; if (needed_in_size > state->resample_in_size) { free(state->resample_in_buf); state->resample_in_buf = malloc(needed_in_size); if (!state->resample_in_buf) { fprintf(stderr, "无法分配重采样输入缓冲区\n"); return 0; } state->resample_in_size = needed_in_size; } size_t needed_out_size = needed_in_size * 2; if (needed_out_size > state->resample_out_size) { free(state->resample_out_buf); state->resample_out_buf = malloc(needed_out_size); if (!state->resample_out_buf) { fprintf(stderr, "无法分配重采样输出缓冲区\n"); return 0; } state->resample_out_size = needed_out_size; } #endif return 1; } // 调整播放速度 void adjust_playback_speed(PlayerState *state, double buffer_level) { // 动态调整播放速度 double target_speed = 1.0; if (buffer_level < MIN_BUFFER_SECONDS) { target_speed = 0.98; } else if (buffer_level > TARGET_BUFFER_SECONDS * 1.5) { target_speed = 1.02; } else { target_speed = 1.0; } // 平滑过渡 state->playback_speed = 0.9 * state->playback_speed + 0.1 * target_speed; if (state->playback_speed < 0.9) state->playback_speed = 0.9; if (state->playback_speed > 1.1) state->playback_speed = 1.1; } // 播放线程函数 void *play_thread(void *arg) { PlayerState *state = (PlayerState *)arg; struct data_queue *q = (struct data_queue *)((char *)arg + sizeof(PlayerState)); float *buffer; size_t buffer_size = BUFFER_SIZE * sizeof(float); size_t done; int status; int error; if (posix_memalign((void**)&buffer, 16, buffer_size)) { fprintf(stderr, "内存对齐分配失败\n"); return NULL; } printf("等待音频数据...\n"); state->buffering = 1; state->buffer_level = 0.0; state->playback_speed = 1.0; size_t initial_buffer_target = BYTES_PER_SECOND * INITIAL_BUFFER_SECONDS; set_buffer_target(q, NULL, initial_buffer_target); state->buffering = 0; struct timespec last_frame, current_frame; clock_gettime(CLOCK_MONOTONIC, &last_frame); srand(time(NULL)); int download_completed = 0; // 跟踪下载状态 while (1) { pthread_mutex_lock(&state->mutex); if (state->stop) { pthread_mutex_unlock(&state->mutex); break; } pthread_mutex_unlock(&state->mutex); state->buffer_level = (double)q->total_size / BYTES_PER_SECOND; adjust_playback_speed(state, state->buffer_level); // 只有在下载未完成时才检查缓冲不足 if (!download_completed && state->buffer_level < MIN_BUFFER_SECONDS && !state->buffering) { state->buffering = 1; printf("\n缓冲不足 (%.1f秒 < %.1f秒), 重新缓冲...\n", state->buffer_level, MIN_BUFFER_SECONDS); size_t buffer_target = BYTES_PER_SECOND * TARGET_BUFFER_SECONDS; set_buffer_target(q, NULL, buffer_target); state->buffering = 0; printf("缓冲恢复,继续播放\n"); } struct data_chunk *chunk = queue_pop(q); // 检测下载完成 if (chunk == NULL) { download_completed = 1; // 标记下载已完成 // 播放解码器中剩余的数据 int more_data = 1; while (more_data) { status = mpg123_read(state->mh, (unsigned char*)buffer, buffer_size, &done); if (status == MPG123_NEED_MORE) { // 没有更多数据了 more_data = 0; } else if (status == MPG123_DONE) { printf("播放完成\n"); more_data = 0; } else if (status != MPG123_OK) { if (status != MPG123_NEW_FORMAT) { fprintf(stderr, "解码错误: %s\n", mpg123_strerror(state->mh)); more_data = 0; } } if (done > 0) { size_t frames = done / (sizeof(float) * state->channels); if (state->format_initialized) { apply_lowpass(buffer, frames * state->channels, state->channels); size_t pcm_size = frames * sizeof(short) * state->channels; short *pcm_buffer = malloc(pcm_size); apply_dither(pcm_buffer, buffer, frames * state->channels); if (pa_simple_write(state->pa_stream, pcm_buffer, pcm_size, &error) < 0) { fprintf(stderr, "PulseAudio播放失败: %s\n", pa_strerror(error)); } free(pcm_buffer); // 高精度播放控制 clock_gettime(CLOCK_MONOTONIC, &current_frame); double elapsed = (current_frame.tv_sec - last_frame.tv_sec) + (current_frame.tv_nsec - last_frame.tv_nsec) / 1e9; double expected_time = (double)frames / state->rate / state->playback_speed; if (elapsed < expected_time) { double sleep_seconds = expected_time - elapsed; usleep((useconds_t)(sleep_seconds * 1000000)); } clock_gettime(CLOCK_MONOTONIC, &last_frame); } } } // 确保所有音频数据都已播放 if (state->format_initialized && state->pa_stream) { if (pa_simple_drain(state->pa_stream, &error) < 0) { fprintf(stderr, "PulseAudio drain失败: %s\n", pa_strerror(error)); } } break; } if (mpg123_feed(state->mh, chunk->data, chunk->size) != MPG123_OK) { fprintf(stderr, "喂入数据失败: %s\n", mpg123_strerror(state->mh)); free(chunk->data); free(chunk); mpg123_close(state->mh); if (mpg123_open_feed(state->mh) != MPG123_OK) { fprintf(stderr, "无法重置解码器\n"); break; } continue; } free(chunk->data); free(chunk); while (1) { status = mpg123_read(state->mh, (unsigned char*)buffer, buffer_size, &done); if (status == MPG123_NEW_FORMAT) { long rate; int channels, encoding; if (mpg123_getformat(state->mh, &rate, &channels, &encoding) == MPG123_OK) { printf("检测到新音频格式: %ld Hz, %d 声道\n", rate, channels); if (!init_pulse_audio(state, rate, channels)) { fprintf(stderr, "无法初始化音频设备\n"); break; } } continue; } if (status == MPG123_NEED_MORE) { break; } if (status != MPG123_OK) { if (status != MPG123_DONE) { fprintf(stderr, "解码错误: %s\n", mpg123_strerror(state->mh)); mpg123_close(state->mh); if (mpg123_open_feed(state->mh) != MPG123_OK) { fprintf(stderr, "无法重置解码器\n"); break; } } break; } size_t frames = done / (sizeof(float) * state->channels); if (state->format_initialized && done > 0) { apply_lowpass(buffer, frames * state->channels, state->channels); size_t pcm_size = frames * sizeof(short) * state->channels; short *pcm_buffer = malloc(pcm_size); apply_dither(pcm_buffer, buffer, frames * state->channels); if (pa_simple_write(state->pa_stream, pcm_buffer, pcm_size, &error) < 0) { fprintf(stderr, "PulseAudio播放失败: %s\n", pa_strerror(error)); } free(pcm_buffer); clock_gettime(CLOCK_MONOTONIC, &current_frame); double elapsed = (current_frame.tv_sec - last_frame.tv_sec) + (current_frame.tv_nsec - last_frame.tv_nsec) / 1e9; double expected_time = (double)frames / state->rate / state->playback_speed; if (elapsed < expected_time) { double sleep_seconds = expected_time - elapsed; usleep((useconds_t)(sleep_seconds * 1000000)); } clock_gettime(CLOCK_MONOTONIC, &last_frame); } state->buffer_level = (double)q->total_size / BYTES_PER_SECOND; printf("缓冲: %.1fs | 速度: %.3f | 队列: %.1fkb\r", state->buffer_level, state->playback_speed, (double)q->total_size / 1024); fflush(stdout); } } free(buffer); printf("\n播放线程退出\n"); return NULL; } // 下载参数结构 struct DownloadParam { CURL *curl; struct data_queue *queue; struct DownloadStatus *status; // 下载状态 }; // 下载进度显示函数 void display_progress(struct DownloadStatus *status, size_t dlnow) { if (!status->display_active) return; double current_time = (double)clock() / CLOCKS_PER_SEC; // 限制更新频率,每秒最多更新10次 if (current_time - status->last_update < 0.1) { return; } status->last_update = current_time; if (status->total_size > 0) { double progress = (double)dlnow / status->total_size * 100.0; printf("下载进度: %.2f%% (%.2f MB/%.2f MB)\r", progress, (double)dlnow / (1024 * 1024), (double)status->total_size / (1024 * 1024)); } else { printf("已下载: %.2f MB\r", (double)dlnow / (1024 * 1024)); } fflush(stdout); } // 下载进度回调函数 static int progress_callback(void *clientp, curl_off_t dltotal, curl_off_t dlnow, curl_off_t ultotal, curl_off_t ulnow) { struct DownloadStatus *status = (struct DownloadStatus *)clientp; // 如果这是第一次获取文件大小 if (dltotal > 0 && status->total_size == 0) { status->total_size = dltotal; printf("\n文件大小: %.2f MB\n", (double)dltotal / (1024 * 1024)); } // 更新已下载大小 status->downloaded_size = dlnow; // 显示进度 display_progress(status, dlnow); return 0; } // 下载线程函数 void *download_thread_wrapper(void *arg) { struct DownloadParam *param = (struct DownloadParam *)arg; CURL *curl = param->curl; struct data_queue *q = param->queue; struct DownloadStatus *status = param->status; // 设置进度回调 curl_easy_setopt(curl, CURLOPT_XFERINFOFUNCTION, progress_callback); curl_easy_setopt(curl, CURLOPT_XFERINFODATA, status); curl_easy_setopt(curl, CURLOPT_NOPROGRESS, 0L); // 初始化下载状态 status->total_size = 0; status->downloaded_size = 0; status->last_update = 0; status->display_active = 1; CURLcode res = curl_easy_perform(curl); if (res != CURLE_OK) { fprintf(stderr, "\n下载失败: %s\n", curl_easy_strerror(res)); } else { // 确保显示最终进度 status->display_active = 1; display_progress(status, status->downloaded_size); printf("\n下载完成, 总大小: %.2f MB\n", (double)status->downloaded_size / (1024 * 1024)); } // 标记下载完成 queue_finish(q); return NULL; } int main() { CURL *curl; struct { PlayerState state; struct data_queue queue; } shared_data = {0}; pthread_t play_tid, download_tid; queue_init(&shared_data.queue); if (mpg123_init() != MPG123_OK) { fprintf(stderr, "无法初始化mpg123\n"); return 1; } shared_data.state.mh = mpg123_new(NULL, NULL); if (!shared_data.state.mh) { fprintf(stderr, "创建mpg123句柄失败: %s\n", mpg123_plain_strerror(mpg123_errcode(NULL))); mpg123_exit(); return 1; } if (mpg123_format_none(shared_data.state.mh) != MPG123_OK || mpg123_format(shared_data.state.mh, 44100, MPG123_STEREO, MPG123_ENC_FLOAT_32) != MPG123_OK) { fprintf(stderr, "无法设置输出格式\n"); mpg123_delete(shared_data.state.mh); mpg123_exit(); return 1; } #if USE_RESAMPLING printf("启用高质量重采样 (48kHz)\n"); shared_data.state.target_rate = 48000; #else printf("禁用重采样\n"); #endif #ifdef MPG123_QUIET mpg123_param(shared_data.state.mh, MPG123_ADD_FLAGS, MPG123_QUIET, 0); #else mpg123_param(shared_data.state.mh, MPG123_VERBOSE, 0, 0.0); #endif mpg123_param(shared_data.state.mh, MPG123_ADD_FLAGS, MPG123_SKIP_ID3V2, 0); mpg123_param(shared_data.state.mh, MPG123_ADD_FLAGS, MPG123_IGNORE_INFOFRAME, 0); mpg123_param(shared_data.state.mh, MPG123_ADD_FLAGS, MPG123_AUTO_RESAMPLE, 0); if (mpg123_open_feed(shared_data.state.mh) != MPG123_OK) { fprintf(stderr, "无法打开feed模式: %s\n", mpg123_strerror(shared_data.state.mh)); mpg123_delete(shared_data.state.mh); mpg123_exit(); return 1; } shared_data.state.format_initialized = 0; shared_data.state.stop = 0; shared_data.state.buffering = 1; shared_data.state.buffer_bytes = 0; shared_data.state.buffer_level = 0.0; shared_data.state.playback_speed = 1.0; #if USE_RESAMPLING shared_data.state.resampler = NULL; shared_data.state.resample_in_buf = NULL; shared_data.state.resample_out_buf = NULL; shared_data.state.resample_in_size = 0; shared_data.state.resample_out_size = 0; #endif if (pthread_mutex_init(&shared_data.state.mutex, NULL)) { fprintf(stderr, "无法初始化互斥锁\n"); mpg123_delete(shared_data.state.mh); mpg123_exit(); return 1; } curl_global_init(CURL_GLOBAL_DEFAULT); curl = curl_easy_init(); if (!curl) { fprintf(stderr, "初始化libcurl失败\n"); return 1; } // 使用普通MP3文件URL const char *url = "https://ting8.yymp3.com/new27/liyugang6/6.mp3"; printf("使用URL: %s\n", url); curl_easy_setopt(curl, CURLOPT_URL, url); curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback); curl_easy_setopt(curl, CURLOPT_WRITEDATA, &shared_data.queue); curl_easy_setopt(curl, CURLOPT_USERAGENT, "Lavf/60.3.100"); struct curl_slist *headers = NULL; headers = curl_slist_append(headers, "Accept: */*"); headers = curl_slist_append(headers, "Range: bytes=0-"); headers = curl_slist_append(headers, "Connection: keep-alive"); curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers); curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L); curl_easy_setopt(curl, CURLOPT_MAXREDIRS, 10L); curl_easy_setopt(curl, CURLOPT_TIMEOUT, 0L); curl_easy_setopt(curl, CURLOPT_NOSIGNAL, 1L); curl_easy_setopt(curl, CURLOPT_BUFFERSIZE, 65536L); curl_easy_setopt(curl, CURLOPT_VERBOSE, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L); curl_easy_setopt(curl, CURLOPT_SSL_VERIFYHOST, 0L); // 创建下载状态结构 struct DownloadStatus download_status = {0}; printf("正在下载音频文件...\n"); // 创建下载线程参数 struct DownloadParam param = { .curl = curl, .queue = &shared_data.queue, .status = &download_status }; // 创建下载线程 if (pthread_create(&download_tid, NULL, download_thread_wrapper, &param) != 0) { fprintf(stderr, "无法创建下载线程\n"); curl_easy_cleanup(curl); curl_global_cleanup(); return 1; } // 创建播放线程 if (pthread_create(&play_tid, NULL, play_thread, &shared_data) != 0) { fprintf(stderr, "无法创建播放线程\n"); mpg123_delete(shared_data.state.mh); mpg123_exit(); return 1; } // 等待下载线程完成 pthread_join(download_tid, NULL); // 等待播放线程结束 pthread_join(play_tid, NULL); // 清理资源 curl_slist_free_all(headers); curl_easy_cleanup(curl); curl_global_cleanup(); queue_cleanup(&shared_data.queue); if (shared_data.state.format_initialized && shared_data.state.pa_stream) { pa_simple_free(shared_data.state.pa_stream); } #if USE_RESAMPLING if (shared_data.state.resampler) { src_delete(shared_data.state.resampler); } free(shared_data.state.resample_in_buf); free(shared_data.state.resample_out_buf); #endif if (shared_data.state.mh) { mpg123_close(shared_data.state.mh); mpg123_delete(shared_data.state.mh); } mpg123_exit(); printf("播放结束\n"); return 0; }
最新发布
07-04
<think>我们正在处理一个音频播放程序,特别是MP3播放器。用户希望优化缓冲逻辑,使得在MP3文件播放完毕后不再重新缓冲,而是直接停止播放。根据引用[1],我们知道播放控制层通常有一个状态机来管理播放状态(播放、暂停、停止等)。因此,我们需要在状态机中增加对文件结束(EOF)的处理。另外,引用[2]展示了音频处理层如何使用LAME库进行编码(这里是转换,但播放时是解码),我们可以参考其数据读取和处理的循环结构。问题分析:在播放过程中,程序通常会有一个缓冲队列,用于存储从文件中读取并解码的音频数据。当文件播放完毕时,我们应该检测到文件结束(EOF)并停止缓冲。解决方案:1.在读取文件的循环中,当读取到文件末尾时,设置一个标志(例如eof_reached)。2.在缓冲逻辑中,当缓冲队列为空且已经到达文件末尾时,不再尝试填充缓冲队列,而是触发停止播放。步骤:假设我们有一个主循环,它从文件读取数据,解码并放入缓冲区(用于播放)。同时,另一个线程(或中断服务程序)从缓冲区读取数据并播放。修改点:1.在读取数据的函数中,当读取到文件末尾时,设置一个全局标志(或通过状态机传递)表示文件已经结束。2.当播放器消耗完缓冲区中的数据并且检测到文件结束标志时,播放器应该停止。具体代码修改思路(伪代码):原始缓冲逻辑可能类似: while(playing) {if(buffer_needs_refill){read_data_from_file_and_decode();//这里可能会阻塞直到有数据if(read_result== EOF) {//文件结束,设置标志set_eof_flag();break;//退出填充循环}//将解码后的数据放入缓冲区}//可能还有其他的状态检查}但是,我们需要在播放线程中检测EOF和缓冲区空,以停止播放。另一种常见设计是使用两个线程:-一个线程(生产者)负责读取文件、解码并填充缓冲区。-另一个线程(消费者)负责从缓冲区取数据并播放。修改建议:-生产者线程在遇到EOF时,除了设置标志外,还应该通知播放线程(消费者)文件结束。-消费者线程在从缓冲区取数据时,如果发现缓冲区为空且EOF标志已设置,则触发停止播放。在状态机中(引用[1]),我们可能有一个状态转换:当处于播放状态时,如果遇到EOF且缓冲区为空,则转换为停止状态。代码示例(简化):假设我们有一个全局结构体来维护播放状态:```ctypedef struct{volatile booleof;//文件结束标志volatilebool buffer_empty;//缓冲区是否为空//其他状态...} player_state_t; //生产者线程(填充缓冲区) void*producer_thread(void *arg){while (!player_state.eof){//读取一段数据intbytes_read= read_from_file(buffer, sizeof(buffer));if(bytes_read ==0) {//到达文件末尾player_state.eof =true;break;}//解码并放入环形缓冲区(这里省略解码步骤)put_into_playback_buffer(decoded_data);}returnNULL;} //消费者线程(播放)void *consumer_thread(void *arg){while (player_state.playing) {//从缓冲区获取数据data= get_from_playback_buffer();if(data== NULL) {//缓冲区为空if(player_state.eof){//文件结束且缓冲区空,停止播放player_state.playing =false;break;}else {//等待数据usleep(1000);continue;}}//播放数据play_audio(data);}//停止后更新状态机change_state(STOP);returnNULL;} ```注意:上述代码是简化的,实际中需要考虑线程同步(使用互斥锁或信号量)来保护共享状态(如player_state)和缓冲区。另一种方法:在状态机中增加一个状态“播放结束”(PLAYBACK_FINISHED),当检测到EOF且缓冲区为空时,转换到该状态,然后自动停止。参考引用[3]中关于用户界面的描述,当播放停止时,用户界面应该更新(例如,暂停按钮变回播放按钮)。因此,修改缓冲逻辑的关键点:-正确设置文件结束标志(EOF-在播放线程中检测EOF和缓冲区空的条件,然后停止播放。相关问题:1.如何检测MP3文件的结束位置?-在解码时,解码器通常会返回一个特定的值(如0)表示已经读完所有数据。2.如何避免在文件结束后继续缓冲?-通过设置EOF标志,并在填充缓冲区的循环中检查该标志。3.如何处理缓冲区中剩余的数据?-即使设置了EOF,消费者线程仍然会播放完缓冲区中剩余的数据,直到缓冲区为空才停止。最后,根据引用[4],在嵌入式系统(如STM32F103)中,我们还需要考虑资源限制和实时性,但基本逻辑是类似的。因此,针对用户的问题,修改缓冲逻辑的核心就是设置EOF标志并在合适的时机(缓冲区空且EOF)停止播放。</think>### 优化MP3播放器缓冲逻辑的实现方案 要解决MP3文件播放完毕后继续缓冲的问题,关键在于**在音频处理层精确检测文件结束(EOF)状态**,并**同步更新播放控制层的状态机**。以下是具体实现步骤: #### 1. 修改缓冲控制逻辑 在音频解码循环中加入EOF检测机制,当检测到文件结束时立即停止缓冲: ```c // 音频处理层 - 解码线程 void* decode_thread(void* arg) { while(player_state == PLAYING) { // 读取并解码数据 int bytes_decoded = mp3_decoder_decode(chunk_buffer, CHUNK_SIZE); /* 新增EOF检测逻辑 */ if (bytes_decoded == 0) { // 解码器返回0表示EOF pthread_mutex_lock(&state_mutex); player_state = EOF_REACHED; // 设置特殊状态 pthread_cond_signal(&buffer_cond); // 唤醒播放线程 pthread_mutex_unlock(&state_mutex); break; // 退出解码循环 } // 将解码数据放入环形缓冲区 ringbuf_put(decoded_buffer, chunk_buffer, bytes_decoded); } return NULL; } ``` #### 2. 更新播放控制层状态机 在状态机中增加`EOF_REACHED`状态,并修改播放线程行为: ```c // 播放控制层 - 状态机处理 enum PlayerState { STOPPED, PLAYING, PAUSED, EOF_REACHED }; // 新增EOF状态 // 音频播放线程 void* playback_thread(void* arg) { while(1) { pthread_mutex_lock(&state_mutex); /* 关键修改:检测EOF状态 */ if (player_state == EOF_REACHED && ringbuf_empty(decoded_buffer)) { player_state = STOPPED; // 缓冲区空且EOF时停止 update_ui(STOPPED); // 更新UI[^3] pthread_mutex_unlock(&state_mutex); break; } // 正常播放逻辑(略) pthread_mutex_unlock(&state_mutex); play_audio_data(); } return NULL; } ``` #### 3. 优化环形缓冲区管理 在缓冲区操作函数中加入状态检查: ```c // 环形缓冲区读取函数 size_t ringbuf_get(RingBuffer* rb, uint8_t* dest, size_t len) { pthread_mutex_lock(&rb->mutex); /* 新增:当EOF状态且缓冲区空时返回0 */ if (player_state == EOF_REACHED && rb->count == 0) { pthread_mutex_unlock(&rb->mutex); return 0; // 通知播放线程结束 } // 正常读取逻辑(略) pthread_mutex_unlock(&rb->mutex); return actual_read; } ``` #### 4. 用户界面同步 根据引用[3]的UI设计原则,在状态变化时更新界面: ```c void update_ui(enum PlayerState state) { switch(state) { case STOPPED: set_button_icon(PLAY_BUTTON, PLAY_ICON); // 重置播放按钮 show_status("播放结束"); // 显示状态提示 break; // 其他状态处理... } } ``` ### 关键实现要点 1. **双重检测机制** - 解码线程检测EOF并设置状态标志 - 播放线程同时检查`EOF_REACHED`状态和缓冲区空状态 2. **线程同步** 使用互斥锁(`pthread_mutex`)保护共享状态变量,通过条件变量(`pthread_cond`)实现线程间高效通信 3. **状态机扩展** 新增`EOF_REACHED`过渡状态,确保在缓冲区数据完全消耗后才切换到`STOPPED`状态 4. **资源释放** 在停止播放后立即释放解码器资源: ```c if (player_state == STOPPED) { mp3_decoder_cleanup(); // 释放解码器资源[^2] close_audio_device(); } ``` ### 稳定性考量 根据引用[4]的稳定性验证要求,建议: 1. 添加边界测试用例:0字节文件、损坏的MP3文件 2. 监控缓冲区状态:记录`EOF_REACHED`到`STOPPED`的转换延迟 3. 压力测试:连续播放100+文件验证资源释放是否彻底 此方案通过精确的状态管理,确保播放结束时: - ✅ 立即停止数据缓冲 - ✅ 完全释放解码资源 - ✅ 同步更新用户界面 - ✅ 避免空指针访问等内存错误 ---
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值