mpg123 把mp3文件解码到标准输出设备(stdout)

博客给出了一条信息技术相关的命令,即使用mpg123工具循环播放音频文件x.mp3的命令:mpg123 --loop -1 --au - x.mp3 。

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


mpg123 --loop -1 --au - x.mp3

./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
import json import requests import os import time from datetime import datetime, timedelta import pytz from mutagen.mp3 import MP3 from mutagen.mp4 import MP4 import subprocess import platform def get_broadcast_data(): """ 获取并提取播报数据 """ # 获取 tenant_access_token tenant_access_token = get_auth_token() if not tenant_access_token: print("获取 tenant_access_token 失败!") return [] # 获取 Feishu Bitable 数据 url = 'https://open.feishu.cn/open-apis/bitable/v1/apps/E1zybPqiqa0TaesZjKKch5ZcnJd/tables/tblwFY4k3pmrV5WK/records/search' headers = { 'Content-Type': 'application/json', 'Authorization': f'Bearer {tenant_access_token}' # 使用获取到的 token } data = {} # 如果需要传递查询条件,可以在这里添加 try: response = requests.post(url, headers=headers, json=data) response.raise_for_status() # 如果响应失败,将抛出异常 response_dict = response.json() # 将返回的 JSON 数据转换为字典 items = response_dict.get("data", {}).get("items", []) data = [] for item in items: fields = item.get("fields", {}) data.append({ "播音日期": extract_broadcast_date(fields, '播音日期'), "时间段": extract_time_segment(fields, '时间段'), "开播音乐file_token": extract_file_token(fields, '开播音乐'), "开场白-播报file_token": extract_file_token(fields, '开场白-播报'), "需更新文案-播报file_token": extract_file_token(fields, '需更新文案-播报'), "壹首歌file_token": extract_file_token(fields, '壹首歌'), "需更新文案2-播报file_token": extract_file_token(fields, '需更新文案2-播报'), "贰首歌file_token": extract_file_token(fields, '贰首歌'), "结束语-播报file_token": extract_file_token(fields, '结束语-播报'), "结束音乐file_token": extract_file_token(fields, '结束音乐') }) return data except requests.exceptions.HTTPError as http_err: print(f"HTTP 错误发生: {http_err}") except Exception as err: print(f"其他错误发生: {err}") return [] def extract_file_token(fields, field_name): """提取 file_token""" field_data = fields.get(field_name, []) if isinstance(field_data, list) and len(field_data) > 0: value = field_data[0] if isinstance(value, dict): return value.get("file_token", "") return '' def extract_time_segment(fields, field_name): """提取时间段字段""" field_data = fields.get(field_name, []) if isinstance(field_data, list) and len(field_data) > 0: value = field_data[0] if isinstance(value, dict): return value.get("text", "") return None def extract_broadcast_date(fields, field_name): """提取播音日期字段""" field_data = fields.get(field_name, 0) if isinstance(field_data, int): try: timestamp = field_data / 1000 # 时间戳转化为秒 parsed_date = datetime.fromtimestamp(timestamp, tz=pytz.utc).astimezone(pytz.timezone('Asia/Shanghai')) return parsed_date.strftime("%Y-%m-%d") # 转换为 "YYYY-MM-DD" 格式 except (ValueError, OverflowError): pass return None def get_auth_token(): """获取认证 token""" url = "https://open.feishu.cn/open-apis/auth/v3/tenant_access_token/internal" headers = {"Content-Type": "application/json; charset=utf-8"} payload = {"app_id": "cli_a882683e8779d00c", "app_secret": "3NKkALA7vyMRVnpKJinmrb1LJ7YuK4H0"} try: response = requests.post(url, json=payload, headers=headers) response.raise_for_status() data = response.json() if data["code"] == 0: return data["tenant_access_token"] else: print(f"请求失败:{data['msg']}(错误码:{data['code']})") except requests.exceptions.RequestException as e: print(f"请求异常:{e}") return None def create_folder(folder_name): """创建文件夹""" if not os.path.exists(folder_name): os.makedirs(folder_name) def download_file(file_token, save_path, authorization): """下载文件""" url = f"https://open.feishu.cn/open-apis/drive/v1/medias/{file_token}/download" headers = {"Authorization": "Bearer " + authorization} try: response = requests.get(url, headers=headers, stream=True) if response.status_code == 200: with open(save_path, 'wb') as f: for chunk in response.iter_content(chunk_size=8192): f.write(chunk) print(f"文件已成功下载到: {save_path}") return True else: print(f"请求失败,状态码: {response.status_code}") print(f"错误信息: {response.text}") except Exception as e: print(f"发生异常: {str(e)}") return False def get_audio_duration(file_path): """获取音频时长""" try: if file_path.endswith(".mp3"): audio = MP3(file_path) elif file_path.endswith(".mp4"): audio = MP4(file_path) else: print(f"不支持的文件格式: {file_path}") return 0 return audio.info.length except Exception as e: print(f"获取音频时长失败: {e}") return 0 def kill_previous_players(): """清理之前残留的播放器进程""" system = platform.system() # 获取当前操作系统类型 try: if system == "Windows": subprocess.run(['taskkill', '/F', '/IM', 'wmplayer.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) subprocess.run(['taskkill', '/F', '/IM', 'vlc.exe'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif system == "Darwin": # macOS subprocess.run(['killall', 'afplay'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) elif system == "Linux": subprocess.run(['pkill', 'mpg123'], stdout=subprocess.PIPE, stderr=subprocess.PIPE) print("已清理之前残留的播放器进程") except Exception as e: print(f"清理播放器进程时发生错误: {e}") def play_music_in_folder(folder_path): """播放文件夹中的音频文件,并在播放完成后关闭播放器""" audio_files = [f for f in os.listdir(folder_path) if f.endswith((".mp3", ".mp4"))] processes = [] # 用于存储播放器进程 for file_name in audio_files: full_file_path = os.path.join(folder_path, file_name) try: duration = get_audio_duration(full_file_path) if duration <= 0: print(f"无法获取 {file_name} 的时长,跳过播放") continue print(f"播放 {file_name},预计播放时长:{duration} 秒") if os.name == 'nt': # Windows 系统 process = subprocess.Popen(['start', '', full_file_path], shell=True) elif os.name == 'posix': # MacOS 或 Linux 系统 process = subprocess.Popen(['afplay', full_file_path]) # 对于 MacOS 使用 afplay else: print(f"不支持的操作系统类型: {os.name}") continue processes.append(process) # 保存进程对象 time.sleep(duration) # 等待音频播放完成 except Exception as e: print(f"无法播放 {full_file_path}: {e}") # 关闭所有播放器进程 for process in processes: try: if process.poll() is None: # 检查进程是否仍在运行 process.kill() # 强制终止进程 print("播放器已强制关闭") except Exception as e: print(f"关闭播放器失败: {e}") def process_time_segment(segment, download_offset, play_offset, data, authorization, folder_name): """处理一个时间段的下载和播放""" target_data = next((entry for entry in data if entry["时间段"] == segment), None) if not target_data: print(f"未找到时间段 {segment} 的文件数据!") return current_time = datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M') segment_start_time = segment.split("-")[0] # 如果当前时间已经超过该时间段的开始时间,则跳过 if current_time > segment_start_time: print(f"当前时间已超过时间段 {segment} 的开始时间,跳过该时间段的处理") return # 等待到达下载时间 download_time = (datetime.strptime(segment_start_time, "%H:%M") - timedelta(minutes=download_offset)).strftime("%H:%M") while current_time != download_time: current_time = datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M') time.sleep(1) print(f"开始下载 {segment} 的文件") files = [] file_tokens = [ ("开播音乐file_token", "mp3"), ("开场白-播报file_token", "mp4"), ("需更新文案-播报file_token", "mp4"), ("壹首歌file_token", "mp3"), ("需更新文案2-播报file_token", "mp4"), ("贰首歌file_token", "mp3"), ("结束语-播报file_token", "mp4"), ("结束音乐file_token", "mp3") ] for i, (key, file_format) in enumerate(file_tokens): token = target_data.get(key) if token: save_path = os.path.join(folder_name, f"file_{i+1}.{file_format}") if download_file(token, save_path, authorization): files.append(save_path) # 清理之前残留的播放器进程(调整到这里) kill_previous_players() # 等待到达播放时间 play_time = (datetime.strptime(segment_start_time, "%H:%M") - timedelta(minutes=play_offset)).strftime("%H:%M") while current_time != play_time: current_time = datetime.now(pytz.timezone('Asia/Shanghai')).strftime('%H:%M') time.sleep(1) print(f"开始播放 {segment} 的文件") play_music_in_folder(folder_name) # 播放结束后再次清理播放器进程 kill_previous_players() # 删除下载的文件 for file in files: os.remove(file) print(f"已删除文件: {file}") def main(): """主函数""" data = get_broadcast_data() if not data: print("未获取到有效的数据!") return authorization = get_auth_token() if not authorization: return folder_name = "bobao" create_folder(folder_name) segments = [ ("15:00-15:10", 10, 0) # 提前10分钟下载文件,0-准点播放 ] for segment, download_offset, play_offset in segments: process_time_segment(segment, download_offset, play_offset, data, authorization, folder_name) # 主程序结束后,再清理一次播放器进程 kill_previous_players() if __name__ == "__main__": main() 上述代码中,播放开场音乐和结束音乐没有播放完,但是获取的播放时长正确
06-05
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值