#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <unistd.h>
#include <pthread.h>
#include <curl/curl.h>
#include <neaacdec.h>
#include <pulse/simple.h>
#include <pulse/error.h>
#include <arpa/inet.h>
#include <libswresample/swresample.h>
#include <math.h>
#include <time.h> // 添加时间支持
#define HLS_URL "https://ngcdn001.cnr.cn/live/zgzs/index.m3u8"
#define USER_AGENT "Mozilla/5.0 (X11; Linux x86_64)"
#define MAX_SEGMENTS 20
#define OUTPUT_SAMPLE_RATE 48000 // 48000Hz 匹配AAC流
#define CHANNELS 2
#define BUFFER_DURATION_MS 200 // 增加缓冲区大小
#define TS_PACKET_SIZE 188
#define SYNC_BYTE 0x47
#define BUFFER_THRESHOLD 3 // 最少缓冲分段数
// 定义MemoryChunk结构体
typedef struct {
unsigned char* data;
size_t size;
} MemoryChunk;
typedef struct {
char* url;
int index;
} Segment;
typedef struct {
CURL* curl;
char* base_url;
Segment segments[MAX_SEGMENTS];
int segment_count;
int last_sequence;
int running;
pthread_mutex_t mutex;
pa_simple* pulse;
NeAACDecHandle aac_decoder;
int aac_initialized;
unsigned long sample_rate; // 实际的采样率
unsigned char channels; // 实际的声道数
SwrContext* swr_ctx; // 重采样上下文
int64_t audio_pts; // 音频时间戳
struct timespec last_play_time; // 最后播放时间
int64_t played_samples; // 已播放样本数
} PlayerState;
// 内存写入回调函数
static size_t write_callback(void* contents, size_t size, size_t nmemb, void* userp) {
size_t realsize = size * nmemb;
MemoryChunk* mem = (MemoryChunk*)userp;
// 使用临时指针避免内存泄漏
unsigned char* new_data = realloc(mem->data, mem->size + realsize + 1);
if (new_data == NULL) {
return 0;
}
mem->data = new_data;
memcpy(&(mem->data[mem->size]), contents, realsize);
mem->size += realsize;
mem->data[mem->size] = '\0';
return realsize;
}
// 下载URL到内存
MemoryChunk download_url(CURL* curl, const char* url) {
MemoryChunk chunk = {NULL, 0};
curl_easy_setopt(curl, CURLOPT_URL, url);
curl_easy_setopt(curl, CURLOPT_WRITEFUNCTION, write_callback);
curl_easy_setopt(curl, CURLOPT_WRITEDATA, &chunk);
curl_easy_setopt(curl, CURLOPT_USERAGENT, USER_AGENT);
curl_easy_setopt(curl, CURLOPT_FOLLOWLOCATION, 1L);
curl_easy_setopt(curl, CURLOPT_TIMEOUT, 5L);
curl_easy_setopt(curl, CURLOPT_ACCEPT_ENCODING, ""); // 启用压缩
curl_easy_setopt(curl, CURLOPT_TCP_KEEPALIVE, 1L); // 启用TCP保活
CURLcode res = curl_easy_perform(curl);
if (res != CURLE_OK) {
fprintf(stderr, "下载失败: %s - %s\n", url, curl_easy_strerror(res));
if (chunk.data) free(chunk.data);
chunk.data = NULL;
chunk.size = 0;
}
return chunk;
}
// 解析M3U8播放列表
int parse_m3u8(PlayerState* state) {
MemoryChunk playlist = download_url(state->curl, HLS_URL);
if (!playlist.data || playlist.size == 0) {
fprintf(stderr, "下载M3U8播放列表失败\n");
return -1;
}
char* data = (char*)playlist.data;
char* saveptr = NULL;
char* line = strtok_r(data, "\n", &saveptr);
int media_sequence = 0;
int segment_count = 0;
while (line != NULL) {
if (strstr(line, "#EXT-X-MEDIA-SEQUENCE:") != NULL) {
sscanf(line, "#EXT-X-MEDIA-SEQUENCE:%d", &media_sequence);
state->last_sequence = media_sequence;
}
else if (strstr(line, ".ts") != NULL && !strstr(line, "#")) {
size_t len = strlen(state->base_url) + strlen(line) + 1;
char* full_url = malloc(len);
if (full_url == NULL) {
fprintf(stderr, "分配内存失败\n");
break;
}
snprintf(full_url, len, "%s%s", state->base_url, line);
int is_new = 1;
for (int i = 0; i < state->segment_count; i++) {
if (state->segments[i].index == media_sequence + segment_count) {
is_new = 0;
break;
}
}
if (is_new && segment_count < MAX_SEGMENTS) {
state->segments[segment_count].url = full_url;
state->segments[segment_count].index = media_sequence + segment_count;
segment_count++;
} else {
free(full_url);
}
}
line = strtok_r(NULL, "\n", &saveptr);
}
state->segment_count = segment_count;
free(playlist.data);
return segment_count;
}
// 从TS包中提取有效载荷
int extract_ts_payload(unsigned char* ts_data, size_t ts_size, unsigned char** payload, size_t* payload_size) {
*payload = NULL;
*payload_size = 0;
size_t pos = 0;
int packet_count = 0;
int valid_packet_count = 0;
while (pos + TS_PACKET_SIZE <= ts_size) {
packet_count++;
// 跳过非同步字节
if (ts_data[pos] != SYNC_BYTE) {
// 尝试重新同步
while (pos < ts_size && ts_data[pos] != SYNC_BYTE) {
pos++;
}
if (pos + TS_PACKET_SIZE > ts_size) break;
}
// 适配字段控制
unsigned char adapt_field = (ts_data[pos + 3] >> 4) & 0x03;
size_t payload_start = pos + 4;
// 处理适配字段
if (adapt_field == 0x02 || adapt_field == 0x03) {
unsigned char adapt_len = ts_data[pos + 4];
payload_start += 1 + adapt_len;
if (payload_start >= pos + TS_PACKET_SIZE) {
// 没有有效载荷
payload_start = pos + TS_PACKET_SIZE;
}
}
// 计算有效载荷大小
size_t packet_payload_size = (pos + TS_PACKET_SIZE) - payload_start;
if (packet_payload_size == 0) {
pos += TS_PACKET_SIZE;
continue;
}
// 重新分配内存并复制数据
unsigned char* new_payload = realloc(*payload, *payload_size + packet_payload_size);
if (!new_payload) {
if (*payload) free(*payload);
*payload = NULL;
*payload_size = 0;
return -1;
}
*payload = new_payload;
memcpy(*payload + *payload_size, ts_data + payload_start, packet_payload_size);
*payload_size += packet_payload_size;
valid_packet_count++;
pos += TS_PACKET_SIZE;
}
if (valid_packet_count == 0) {
fprintf(stderr, "未找到有效TS包\n");
return -1;
}
return 0;
}
// 初始化重采样器
int init_resampler(PlayerState* state) {
if (state->swr_ctx) {
swr_free(&state->swr_ctx);
}
// 创建重采样上下文
state->swr_ctx = swr_alloc_set_opts(NULL,
AV_CH_LAYOUT_STEREO, // 输出声道布局
AV_SAMPLE_FMT_S16, // 输出采样格式
OUTPUT_SAMPLE_RATE, // 输出采样率
state->channels == 1 ? AV_CH_LAYOUT_MONO : AV_CH_LAYOUT_STEREO, // 输入声道布局
AV_SAMPLE_FMT_FLT, // 输入采样格式 (FAAD2输出的是浮点)
state->sample_rate, // 输入采样率
0, NULL);
if (!state->swr_ctx) {
fprintf(stderr, "无法分配重采样上下文\n");
return -1;
}
// 设置重采样质量
av_opt_set_int(state->swr_ctx, "ich", state->channels, 0);
av_opt_set_int(state->swr_ctx, "och", CHANNELS, 0);
av_opt_set_int(state->swr_ctx, "filter_size", 32, 0); // 减少滤波器长度
av_opt_set_int(state->swr_ctx, "phase_shift", 6, 0); // 降低相位偏移
av_opt_set_double(state->swr_ctx, "cutoff", 0.8, 0); // 降低截止频率
av_opt_set_int(state->swr_ctx, "linear_resample", 1, 0); // 使用线性插值
// 初始化重采样器
if (swr_init(state->swr_ctx) < 0) {
fprintf(stderr, "无法初始化重采样器\n");
swr_free(&state->swr_ctx);
return -1;
}
printf("初始化重采样器: %dHz/%dch -> %dHz/%dch\n",
state->sample_rate, state->channels, OUTPUT_SAMPLE_RATE, CHANNELS);
return 0;
}
// 手动浮点到整型转换
void convert_float_to_s16(float* in, int16_t* out, int samples) {
for (int i = 0; i < samples; i++) {
float sample = in[i];
// 应用软裁剪避免削波
if (sample > 1.0f) sample = 1.0f;
else if (sample < -1.0f) sample = -1.0f;
out[i] = (int16_t)(sample * 32767.0f);
}
}
// 音频同步机制
void sync_audio(PlayerState* state, int samples) {
struct timespec now;
clock_gettime(CLOCK_MONOTONIC, &now);
// 如果是第一次播放,初始化时间戳
if (state->played_samples == 0) {
state->last_play_time = now;
state->played_samples = samples;
return;
}
// 计算应播放的时间
double expected_time = (double)state->played_samples / OUTPUT_SAMPLE_RATE;
double actual_time = (now.tv_sec - state->last_play_time.tv_sec) +
(now.tv_nsec - state->last_play_time.tv_nsec) / 1e9;
// 速度偏差超过5%时警告
if (fabs(actual_time - expected_time) > 0.05 * expected_time) {
fprintf(stderr, "音频不同步: 预期 %.3fs 实际 %.3fs (偏差 %.1f%%)\n",
expected_time, actual_time,
100*(actual_time - expected_time)/expected_time);
}
// 更新状态
state->played_samples += samples;
state->last_play_time = now;
}
// 处理AAC帧并解码为PCM
int decode_aac_frames(PlayerState* state, unsigned char* aac_data, size_t aac_size) {
size_t pos = 0;
int decoded_frames = 0;
int initialization_attempts = 0;
// 重采样缓冲区
int16_t* resampled_buffer = NULL;
int resampled_samples = 0;
int max_out_samples = 0;
// 直接转换缓冲区
int16_t* convert_buffer = NULL;
int convert_buffer_size = 0;
while (pos < aac_size) {
// 查找ADTS帧头 (0xFFFx)
if (pos + 7 > aac_size) {
// 剩余数据不足一个ADTS帧头
break;
}
// 检查同步字: 0xFFF (前12位)
if (aac_data[pos] != 0xFF || (aac_data[pos+1] & 0xF0) != 0xF0) {
// 检查是否为MP3格式
if (aac_data[pos] == 0xFF && (aac_data[pos+1] & 0xE0) == 0xE0) {
fprintf(stderr, "检测到MP3格式,当前仅支持AAC解码\n");
return -1;
}
pos++;
continue;
}
// 解析ADTS帧头长度
unsigned int frame_length = ((aac_data[pos+3] & 0x03) << 11);
frame_length |= (aac_data[pos+4] << 3);
frame_length |= (aac_data[pos+5] >> 5);
// 检查帧是否完整
if (frame_length < 7 || pos + frame_length > aac_size) {
// 帧不完整,跳过
pos++;
continue;
}
// 初始化解码器(如果尚未初始化)
if (!state->aac_initialized) {
// 检查AAC特征
if ((aac_data[pos+1] & 0xF6) != 0xF0) {
fprintf(stderr, "非AAC格式! 同步字: %02X%02X\n",
aac_data[pos], aac_data[pos+1]);
return -1;
}
unsigned long sr;
unsigned char ch;
if (NeAACDecInit(state->aac_decoder, aac_data + pos, frame_length, &sr, &ch) < 0) {
fprintf(stderr, "AAC解码器初始化失败,尝试下一个帧\n");
initialization_attempts++;
// 尝试5次后放弃
if (initialization_attempts > 5) {
fprintf(stderr, "无法初始化解码器,跳过此分段\n");
break;
}
pos += frame_length; // 跳过当前帧,尝试下一个
continue;
}
state->sample_rate = sr;
state->channels = ch;
state->aac_initialized = 1;
printf("AAC解码器初始化成功: %lu Hz, %d 声道\n", sr, ch);
// 检查采样率是否在合理范围内
if (state->sample_rate < 8000 || state->sample_rate > 192000) {
fprintf(stderr, "异常采样率: %lu Hz, 使用默认48000Hz\n", state->sample_rate);
state->sample_rate = OUTPUT_SAMPLE_RATE;
}
// 初始化重采样器(如果需要)
if (state->sample_rate != OUTPUT_SAMPLE_RATE || state->channels != CHANNELS) {
if (init_resampler(state) != 0) {
fprintf(stderr, "重采样器初始化失败,继续播放但可能有杂音\n");
}
} else {
printf("输入输出格式匹配,禁用重采样\n");
}
}
// 解码AAC帧
NeAACDecFrameInfo frame_info;
void* pcm = NeAACDecDecode(state->aac_decoder, &frame_info, aac_data + pos, frame_length);
if (frame_info.error == 0 && pcm && frame_info.samples > 0) {
int in_samples = frame_info.samples;
int in_channels = frame_info.channels;
// 更新音频时间戳
state->audio_pts += in_samples;
// 如果需要重采样
if (state->swr_ctx) {
// 配置输入和输出
const uint8_t *in_data[8] = { (const uint8_t*)pcm };
// 计算最大输出样本数
max_out_samples = av_rescale_rnd(swr_get_delay(state->swr_ctx, state->sample_rate) + in_samples,
OUTPUT_SAMPLE_RATE, state->sample_rate, AV_ROUND_UP);
// 分配重采样缓冲区
if (!resampled_buffer) {
resampled_buffer = malloc(max_out_samples * CHANNELS * sizeof(int16_t));
}
uint8_t *out_data[8] = { (uint8_t*)resampled_buffer };
// 执行重采样
resampled_samples = swr_convert(state->swr_ctx, out_data, max_out_samples,
in_data, in_samples);
if (resampled_samples > 0) {
int out_size = resampled_samples * CHANNELS * sizeof(int16_t);
if (pa_simple_write(state->pulse, resampled_buffer, out_size, NULL) < 0) {
fprintf(stderr, "PulseAudio写入错误\n");
} else {
decoded_frames++;
sync_audio(state, resampled_samples);
}
}
} else {
// 不需要重采样,但需要转换到S16
int pcm_size = in_samples * in_channels;
// 确保转换缓冲区足够大
if (convert_buffer_size < pcm_size) {
convert_buffer = realloc(convert_buffer, pcm_size * sizeof(int16_t));
convert_buffer_size = pcm_size;
}
// 手动转换浮点到S16
convert_float_to_s16((float*)pcm, convert_buffer, pcm_size);
int out_size = pcm_size * sizeof(int16_t);
if (pa_simple_write(state->pulse, convert_buffer, out_size, NULL) < 0) {
fprintf(stderr, "PulseAudio写入错误\n");
} else {
decoded_frames++;
sync_audio(state, in_samples);
}
}
} else if (frame_info.error) {
fprintf(stderr, "AAC解码错误: %s\n", NeAACDecGetErrorMessage(frame_info.error));
}
pos += frame_length;
}
// 冲刷重采样器
if (state->swr_ctx) {
int remaining = swr_get_delay(state->swr_ctx, 0);
if (remaining > 0) {
if (!resampled_buffer) {
resampled_buffer = malloc(remaining * CHANNELS * sizeof(int16_t));
}
uint8_t *out_data[8] = { (uint8_t*)resampled_buffer };
resampled_samples = swr_convert(state->swr_ctx, out_data, remaining, NULL, 0);
if (resampled_samples > 0) {
int out_size = resampled_samples * CHANNELS * sizeof(int16_t);
pa_simple_write(state->pulse, resampled_buffer, out_size, NULL);
sync_audio(state, resampled_samples);
}
}
}
// 清理缓冲区
if (resampled_buffer) free(resampled_buffer);
if (convert_buffer) free(convert_buffer);
return decoded_frames;
}
// 处理单个TS分段
void process_ts_segment(PlayerState* state, const char* url) {
struct timespec start, end;
clock_gettime(CLOCK_MONOTONIC, &start);
// 下载TS分段
MemoryChunk ts_chunk = download_url(state->curl, url);
if (!ts_chunk.data || ts_chunk.size == 0) {
fprintf(stderr, "下载分段失败: %s\n", url);
return;
}
// 从TS包中提取有效载荷
unsigned char* payload = NULL;
size_t payload_size = 0;
if (extract_ts_payload(ts_chunk.data, ts_chunk.size, &payload, &payload_size) != 0) {
fprintf(stderr, "TS解析失败: %s\n", url);
free(ts_chunk.data);
return;
}
free(ts_chunk.data);
if (payload && payload_size > 0) {
// 解码AAC音频
int frames = decode_aac_frames(state, payload, payload_size);
if (frames == 0) {
fprintf(stderr, "未解码任何AAC帧: %s\n", url);
}
free(payload);
} else {
fprintf(stderr, "未找到有效载荷: %s\n", url);
}
// 计算处理时间
clock_gettime(CLOCK_MONOTONIC, &end);
double elapsed = (end.tv_sec - start.tv_sec) +
(end.tv_nsec - start.tv_nsec) / 1e9;
printf("分段处理时间: %.2fms\n", elapsed * 1000);
}
// 音频解码和播放线程
void* audio_thread(void* arg) {
PlayerState* state = (PlayerState*)arg;
// 初始化FAAD2解码器
state->aac_decoder = NeAACDecOpen();
if (!state->aac_decoder) {
fprintf(stderr, "无法打开AAC解码器\n");
return NULL;
}
state->aac_initialized = 0;
state->swr_ctx = NULL; // 初始化重采样器为NULL
state->audio_pts = 0; // 初始化时间戳
state->played_samples = 0;
// 配置解码器
NeAACDecConfigurationPtr config = NeAACDecGetCurrentConfiguration(state->aac_decoder);
if (config) {
config->outputFormat = FAAD_FMT_FLOAT; // 改为浮点输出
config->downMatrix = 1;
config->dontUpSampleImplicitSBR = 1; // 防止SBR上采样
NeAACDecSetConfiguration(state->aac_decoder, config);
} else {
fprintf(stderr, "无法获取AAC解码器配置\n");
}
// 设置音频缓冲
const int buffer_duration = BUFFER_DURATION_MS;
const int buffer_size = OUTPUT_SAMPLE_RATE * CHANNELS * sizeof(int16_t) * buffer_duration / 1000;
while (state->running) {
pthread_mutex_lock(&state->mutex);
if (state->segment_count == 0) {
pthread_mutex_unlock(&state->mutex);
usleep(buffer_duration * 1000); // 按缓冲区时长休眠
continue;
}
// 获取一个分段
Segment seg = state->segments[0];
memmove(&state->segments[0], &state->segments[1], (state->segment_count - 1) * sizeof(Segment));
state->segment_count--;
pthread_mutex_unlock(&state->mutex);
// 处理TS分段
process_ts_segment(state, seg.url);
free(seg.url);
}
// 清理资源
if (state->swr_ctx) {
swr_free(&state->swr_ctx);
}
// 清理FAAD2解码器
NeAACDecClose(state->aac_decoder);
return NULL;
}
// 主控制线程
int main() {
PlayerState state;
memset(&state, 0, sizeof(PlayerState));
// 初始化CURL
curl_global_init(CURL_GLOBAL_DEFAULT);
state.curl = curl_easy_init();
if (!state.curl) {
fprintf(stderr, "无法初始化CURL\n");
return 1;
}
// 设置基础URL
state.base_url = "https://ngcdn001.cnr.cn/live/zgzs/";
// 初始化互斥锁
if (pthread_mutex_init(&state.mutex, NULL)) {
fprintf(stderr, "无法初始化互斥锁\n");
curl_easy_cleanup(state.curl);
return 1;
}
// 初始化PulseAudio
static const pa_sample_spec ss = {
.format = PA_SAMPLE_S16LE,
.rate = OUTPUT_SAMPLE_RATE,
.channels = CHANNELS
};
// 增加缓冲区大小 (500ms)
pa_buffer_attr buffer_attr = {
.maxlength = (uint32_t)-1,
.tlength = (uint32_t)(OUTPUT_SAMPLE_RATE * sizeof(int16_t) * CHANNELS * 0.5), // 500ms
.prebuf = (uint32_t)-1,
.minreq = (uint32_t)(OUTPUT_SAMPLE_RATE * sizeof(int16_t) * CHANNELS * 0.05), // 50ms
.fragsize = (uint32_t)-1
};
int pa_error;
state.pulse = pa_simple_new(
NULL, // 默认服务器
"CNR Radio Player", // 应用名称
PA_STREAM_PLAYBACK, // 播放流
NULL, // 默认设备
"China National Radio", // 流描述
&ss, // 采样规格
NULL, // 声道映射
&buffer_attr, // 缓冲区属性
&pa_error // 错误码
);
if (!state.pulse) {
fprintf(stderr, "无法创建PulseAudio连接: %s\n", pa_strerror(pa_error));
pthread_mutex_destroy(&state.mutex);
curl_easy_cleanup(state.curl);
return 1;
}
state.running = 1;
// 创建音频线程
pthread_t audio_tid;
if (pthread_create(&audio_tid, NULL, audio_thread, &state) != 0) {
fprintf(stderr, "无法创建音频线程\n");
pa_simple_free(state.pulse);
pthread_mutex_destroy(&state.mutex);
curl_easy_cleanup(state.curl);
return 1;
}
printf("开始播放中国之声 (按Ctrl+C停止)...\n");
printf("输出采样率: %d Hz, 声道: %d\n", OUTPUT_SAMPLE_RATE, CHANNELS);
printf("缓冲区: %dms, 缓冲阈值: %d个分段\n", BUFFER_DURATION_MS, BUFFER_THRESHOLD);
// 主循环:定期更新播放列表
while (state.running) {
int count = parse_m3u8(&state);
if (count > 0) {
printf("获取到 %d 个新分段, 当前缓冲: %d\n", count, state.segment_count);
} else if (count < 0) {
fprintf(stderr, "解析M3U8失败, 10秒后重试...\n");
sleep(10);
continue;
}
// 动态调整休眠时间
pthread_mutex_lock(&state.mutex);
int current_count = state.segment_count;
pthread_mutex_unlock(&state.mutex);
int sleep_time = current_count > BUFFER_THRESHOLD ? 2 : 1;
sleep(sleep_time);
}
// 清理资源
state.running = 0;
pthread_join(audio_tid, NULL);
// 清理剩余分段
for (int i = 0; i < state.segment_count; i++) {
free(state.segments[i].url);
}
// 刷新PulseAudio缓冲区
pa_simple_drain(state.pulse, NULL);
pa_simple_free(state.pulse);
curl_easy_cleanup(state.curl);
curl_global_cleanup();
pthread_mutex_destroy(&state.mutex);
return 0;
}
此代码使用的什么进行的m3u8解析
最新发布