- 注册
- 2025/02/13
- 消息
- 74
- 柚币
- 2,664.6Y
- 米币
- 0.0M
C++:
// 音乐播放器.cpp - 完整优化版(CSV快速存储歌曲信息)
#include "framework.h"
#include "音乐播放器.h"
#include <commdlg.h>
#include <mmsystem.h>
#include <shlwapi.h>
#include <shlobj.h>
#include <commctrl.h>
#include <tchar.h>
#include <vector>
#include <string>
#include <algorithm>
#include <stdint.h>
#include <thread>
#include <mutex>
#include <condition_variable>
#include <queue>
#include <atomic>
#include <random>
#include <sstream>
#include "sqlite3.h"
// 禁用C++17 deprecated警告
#define _SILENCE_CXX17_CODECVT_HEADER_DEPRECATION_WARNING
// FFmpeg 核心头文件
extern "C" {
#include <libavformat/avformat.h>
#include <libavcodec/avcodec.h>
#include <libswresample/swresample.h>
#include <libavutil/avutil.h>
#include <libavutil/channel_layout.h>
#include <libavutil/samplefmt.h>
}
// 库依赖声明
#pragma comment(lib, "winmm.lib")
#pragma comment(lib, "shlwapi.lib")
#pragma comment(lib, "shell32.lib")
#pragma comment(lib, "comctl32.lib")
#pragma comment(lib, "ole32.lib")
#pragma comment(lib, "avformat.lib")
#pragma comment(lib, "avcodec.lib")
#pragma comment(lib, "swresample.lib")
#pragma comment(lib, "avutil.lib")
// 公共控件初始化
#pragma comment(linker, "\"/manifestdependency:type='win32' name='Microsoft.Windows.Common-Controls' version='6.0.0.0' processorArchitecture='*' publicKeyToken='6595b64144ccf1df' language='*'\"")
// 控件ID与自定义消息定义
#define MAX_LOADSTRING 100
#define IDC_PLAY_BUTTON 1001
#define IDC_PREV_BUTTON 1002
#define IDC_NEXT_BUTTON 1003
#define IDC_ADD_FILE_BUTTON 1004
#define IDC_ADD_FOLDER_BUTTON 1005
#define IDC_CLEAR_LIST_BUTTON 1010
#define IDC_SONG_LIST 1006 // ListView控件
#define IDC_TITLE_LABEL 1007
#define IDC_VOLUME_SLIDER 1008
#define IDC_MODE_BUTTON 1009
#define IDC_SEARCH_EDIT 1011
#define IDC_SEARCH_BUTTON 1012
#define IDC_PROGRESS_BAR 1013 // 进度条控件ID
#define IDC_PROGRESS_TEXT 1014 // 进度文本显示控件
#define WM_USER_PLAY_COMPLETE (WM_USER + 1)
#define WM_USER_UPDATE_PROGRESS (WM_USER + 2) // 更新进度条的自定义消息
#define PROGRESS_UPDATE_INTERVAL 100 // 进度条更新间隔(毫秒)
#define PROGRESS_TIMER_ID 2 // 进度更新定时器ID
// 缓冲区配置
const int N_BUFFERS = 100;
const float MIN_BUFFER_SIZE_MB = 300.0f;
const float MAX_BUFFER_SIZE_MB = 300.0f;
const int MIN_BUFFER_SIZE = static_cast<int>(MIN_BUFFER_SIZE_MB * 1024 * 1024);
const int MAX_BUFFER_SIZE = static_cast<int>(MAX_BUFFER_SIZE_MB * 1024 * 1024);
// 字符串缓冲区大小定义
#define LARGE_BUFFER_SIZE 9999
#define MEDIUM_BUFFER_SIZE 9999
#define SMALL_BUFFER_SIZE 9999
// 播放模式枚举
enum class PlayMode {
AllLoop,
SingleLoop,
Random
};
// 歌曲信息结构体
struct SongInfo {
std::wstring filePath;
std::wstring fileName;
std::wstring fileSize; // 格式化后的文件大小
std::wstring bitrate; // 格式化后的比特率
int64_t sizeBytes; // 原始字节数
int bitrateKbps; // 原始比特率
double duration; // 歌曲时长(秒)
};
// 全局变量
HINSTANCE hInst;
WCHAR szTitle[MAX_LOADSTRING];
WCHAR szWindowClass[MAX_LOADSTRING];
HFONT hFont = NULL;
HFONT hLargeFont = NULL;
HWND g_hwndMain = NULL;
HWND g_hwndSongList = NULL;
HWND g_hwndTitleLabel = NULL;
HWND g_hwndModeButton = NULL;
HWND g_hwndProgressBar = NULL; // 进度条句柄
HWND g_hwndProgressText = NULL; // 进度文本句柄
static sqlite3* g_db = nullptr; // 全局数据库句柄
std::vector<SongInfo> g_songList;
int g_lastUpdatedSongCount = 1;
int g_currentSongIndex = 1;
BOOL g_isPlaying = FALSE;
PlayMode g_currentMode = PlayMode::AllLoop;
std::mt19937 g_randEngine(std::random_device{}());
// FFmpeg与WaveOut上下文
AVFormatContext* fmt_ctx = NULL;
AVCodecContext* dec_ctx = NULL;
SwrContext* swr_ctx = NULL;
AVPacket* pkt = NULL;
AVFrame* frame = NULL;
int audio_stream_idx = -1;
HWAVEOUT hWaveOut = NULL;
std::atomic<bool> is_playing_ffmpeg(false);
double g_totalDuration = 0.0; // 总时长(秒)
std::atomic<double> g_playedDuration = 0.0; // 已播放时长(秒)
const double DURATION_TOLERANCE = 0;
std::atomic<double> g_lastUpdateTime = 0.0; // 上次更新时间(用于实时计算)
std::atomic<double> g_playbackSpeed = 1.0; // 播放速度(用于准确计算)
// 多缓冲结构体
struct PCMBuffer {
WAVEHDR hdr = { 0 };
std::vector<uint8_t> data;
bool prepared = false;
double duration; // 该缓冲区的音频时长(秒)
};
std::vector<PCMBuffer> g_buffers;
std::queue<int> g_freeBuffers;
std::mutex g_bufMutex;
std::condition_variable g_bufCv;
std::thread g_decodeThread;
std::atomic<bool> g_stopDecode(false);
// 函数声明
ATOM MyRegisterClass(HINSTANCE hInstance);
BOOL InitInstance(HINSTANCE, int);
LRESULT CALLBACK WndProc(HWND, UINT, WPARAM, LPARAM);
INT_PTR CALLBACK About(HWND, UINT, WPARAM, LPARAM);
void InitPlayerUI(HWND hWnd);
void PlaySong(const SongInfo& song);
void StopPlayback();
void PausePlayback();
void ResumePlayback();
void AddFiles(HWND hWnd);
void AddFolder(HWND hWnd);
void ClearSongList();
void UpdateSongListUI();
void SavePlaylist();
void LoadPlaylist();
void SetVolume(int volume);
void EnumMusicFiles(const std::wstring& folder);
void InitFFmpeg();
bool OpenAudioFile(const std::wstring& filePath, double& duration);
void CloseAudioFile();
bool DecodeAudioFrame(std::vector<uint8_t>& pcm, double& frameDuration);
void PlayPCMData(const std::vector<uint8_t>& pcm, double duration, int bufIndex);
std::string WstringToUtf8(const std::wstring& wstr);
void UpdateModeButtonText();
int GetNextIndex();
int GetPrevIndex();
void CALLBACK WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance, DWORD_PTR dwParam1, DWORD_PTR dwParam2);
void DecodeThreadFunc();
std::wstring GetFileSizeString(const std::wstring& filePath, int64_t& sizeBytes);
std::wstring GetBitrateString(const std::wstring& filePath, int& bitrateKbps);
std::wstring FormatDuration(double seconds);
void UpdateProgressDisplay();
double GetCurrentTimeSeconds();
// CSV处理辅助函数
std::string WstringToUtf8Escaped(const std::wstring& wstr);
std::wstring Utf8ToWstring(const std::string& utf8);
std::vector<std::string> SplitCsvLine(const std::string& line);
int WINAPI wWinMain(_In_ HINSTANCE hInstance,
_In_opt_ HINSTANCE hPrevInstance,
_In_ LPWSTR lpCmdLine,
_In_ int nCmdShow)
{
UNREFERENCED_PARAMETER(hPrevInstance);
UNREFERENCED_PARAMETER(lpCmdLine);
INITCOMMONCONTROLSEX icex;
icex.dwSize = sizeof(INITCOMMONCONTROLSEX);
icex.dwICC = ICC_BAR_CLASSES | ICC_LISTVIEW_CLASSES;
InitCommonControlsEx(&icex);
wcscpy_s(szTitle, L"音乐播放器");
wcscpy_s(szWindowClass, L"MusicPlayerClass");
MyRegisterClass(hInstance);
if (!InitInstance(hInstance, nCmdShow))
{
return FALSE;
}
HACCEL hAccelTable = LoadAccelerators(hInstance, MAKEINTRESOURCE(IDC_MY));
InitFFmpeg();
LoadPlaylist();
UpdateModeButtonText();
MSG msg;
while (GetMessage(&msg, nullptr, 0, 0))
{
if (hAccelTable && !TranslateAccelerator(msg.hwnd, hAccelTable, &msg))
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
else if (!hAccelTable)
{
TranslateMessage(&msg);
DispatchMessage(&msg);
}
}
SavePlaylist();
StopPlayback();
if (hFont) DeleteObject(hFont);
if (hLargeFont) DeleteObject(hLargeFont);
return (int)msg.wParam;
}
ATOM MyRegisterClass(HINSTANCE hInstance)
{
WNDCLASSEXW wcex;
wcex.cbSize = sizeof(WNDCLASSEX);
wcex.style = CS_HREDRAW | CS_VREDRAW;
wcex.lpfnWndProc = WndProc;
wcex.cbClsExtra = 0;
wcex.cbWndExtra = 0;
wcex.hInstance = hInstance;
wcex.hIcon = LoadIcon(hInstance, MAKEINTRESOURCE(IDI_MY));
wcex.hCursor = LoadCursor(nullptr, IDC_ARROW);
wcex.hbrBackground = (HBRUSH)(COLOR_WINDOW + 1);
wcex.lpszMenuName = MAKEINTRESOURCEW(IDC_MY);
wcex.lpszClassName = szWindowClass;
wcex.hIconSm = LoadIcon(wcex.hInstance, MAKEINTRESOURCE(IDI_SMALL));
return RegisterClassExW(&wcex);
}
BOOL InitInstance(HINSTANCE hInstance, int nCmdShow)
{
hInst = hInstance;
HWND hWnd = CreateWindowW(szWindowClass, szTitle,
WS_OVERLAPPEDWINDOW & ~WS_THICKFRAME & ~WS_MAXIMIZEBOX,
CW_USEDEFAULT, 100, 1000, 650, nullptr, NULL, hInstance, nullptr);
if (!hWnd)
{
return FALSE;
}
g_hwndMain = hWnd;
InitPlayerUI(hWnd);
ShowWindow(hWnd, nCmdShow);
UpdateWindow(hWnd);
return TRUE;
}
void InitPlayerUI(HWND hWnd)
{
hFont = CreateFontW(20, 0, 0, 0, FW_NORMAL,
FALSE, FALSE, FALSE, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"微软雅黑");
hLargeFont = CreateFontW(20, 0, 0, 0, FW_BOLD,
FALSE, FALSE, FALSE, DEFAULT_CHARSET,
OUT_DEFAULT_PRECIS, CLIP_DEFAULT_PRECIS,
DEFAULT_QUALITY, DEFAULT_PITCH | FF_SWISS, L"微软雅黑");
// 播放控制按钮
HWND hPrevBtn = CreateWindowW(L"BUTTON", L"◀",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
20, 20, 50, 50, hWnd, (HMENU)IDC_PREV_BUTTON, hInst, NULL);
HWND hPlayBtn = CreateWindowW(L"BUTTON", L"▶",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
80, 20, 50, 50, hWnd, (HMENU)IDC_PLAY_BUTTON, hInst, NULL);
HWND hNextBtn = CreateWindowW(L"BUTTON", L"▶▶",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
140, 20, 50, 50, hWnd, (HMENU)IDC_NEXT_BUTTON, hInst, NULL);
// 模式切换按钮
g_hwndModeButton = CreateWindowW(L"BUTTON", L"全部循环",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
200, 20, 120, 50, hWnd, (HMENU)IDC_MODE_BUTTON, hInst, NULL);
// 文件操作按钮
HWND hAddFileBtn = CreateWindowW(L"BUTTON", L"添加文件",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
330, 20, 120, 50, hWnd, (HMENU)IDC_ADD_FILE_BUTTON, hInst, NULL);
HWND hAddFolderBtn = CreateWindowW(L"BUTTON", L"添加文件夹",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
460, 20, 120, 50, hWnd, (HMENU)IDC_ADD_FOLDER_BUTTON, hInst, NULL);
HWND hClearListBtn = CreateWindowW(L"BUTTON", L"清空列表",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
600, 20, 120, 50, hWnd, (HMENU)IDC_CLEAR_LIST_BUTTON, hInst, NULL);
// 音量控制
CreateWindowW(L"STATIC", L"音量:",
WS_CHILD | WS_VISIBLE | SS_CENTER,
740, 35, 50, 20, hWnd, NULL, hInst, NULL);
HWND hVolumeSlider = CreateWindowW(TRACKBAR_CLASS, L"",
WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_NOTICKS | TBS_FIXEDLENGTH,
790, 30, 60, 30, hWnd, (HMENU)IDC_VOLUME_SLIDER, hInst, NULL);
SendMessage(hVolumeSlider, TBM_SETRANGE, TRUE, MAKELPARAM(0, 100));
SendMessage(hVolumeSlider, TBM_SETPOS, TRUE, 70);
SetVolume(70);
// 标题(显示歌曲名+文件夹路径)
g_hwndTitleLabel = CreateWindowW(L"STATIC", L"等待播放",
WS_CHILD | WS_VISIBLE | SS_CENTER | WS_BORDER,
20, 80, 900, 40, hWnd, (HMENU)IDC_TITLE_LABEL, hInst, NULL);
// 进度条(可拖动)
g_hwndProgressBar = CreateWindowW(TRACKBAR_CLASS, L"",
WS_CHILD | WS_VISIBLE | TBS_HORZ | TBS_NOTICKS | TBS_FIXEDLENGTH,
20, 125, 800, 30, hWnd, (HMENU)IDC_PROGRESS_BAR, hInst, NULL);
SendMessage(g_hwndProgressBar, TBM_SETRANGE, TRUE, MAKELPARAM(0, 1000)); // 0~1000 精度
SendMessage(g_hwndProgressBar, TBM_SETPOS, TRUE, 0);
// 进度文本显示(显示当前时间/总时间)
g_hwndProgressText = CreateWindowW(L"STATIC", L"00:00 / 00:00",
WS_CHILD | WS_VISIBLE | SS_RIGHT,
830, 135, 90, 20, hWnd, (HMENU)IDC_PROGRESS_TEXT, hInst, NULL);
// 搜索控件
CreateWindowW(L"STATIC", L"搜索:",
WS_CHILD | WS_VISIBLE | SS_LEFT,
20, 165, 50, 25, hWnd, NULL, hInst, NULL);
HWND hSearchEdit = CreateWindowW(L"EDIT", L"",
WS_CHILD | WS_VISIBLE | WS_BORDER | ES_AUTOHSCROLL,
80, 165, 650, 25, hWnd, (HMENU)IDC_SEARCH_EDIT, hInst, NULL);
HWND hSearchBtn = CreateWindowW(L"BUTTON", L"定位",
WS_CHILD | WS_VISIBLE | BS_PUSHBUTTON | BS_CENTER,
740, 165, 80, 25, hWnd, (HMENU)IDC_SEARCH_BUTTON, hInst, NULL);
// 歌曲列表(ListView控件)
g_hwndSongList = CreateWindowW(WC_LISTVIEW, L"",
WS_CHILD | WS_VISIBLE | WS_VSCROLL | WS_HSCROLL | LVS_REPORT | LVS_SHOWSELALWAYS | WS_BORDER,
20, 195, 900, 400, hWnd, (HMENU)IDC_SONG_LIST, hInst, NULL);
// 设置ListView列
LVCOLUMN lvc = { 0 };
lvc.mask = LVCF_FMT | LVCF_WIDTH | LVCF_TEXT | LVCF_SUBITEM;
// 歌曲名列
lvc.fmt = LVCFMT_LEFT;
lvc.cx = 450;
lvc.pszText = const_cast<LPWSTR>(L"歌曲名");
ListView_InsertColumn(g_hwndSongList, 0, &lvc);
// 文件大小列
lvc.fmt = LVCFMT_RIGHT;
lvc.cx = 120;
lvc.pszText = const_cast<LPWSTR>(L"文件大小");
ListView_InsertColumn(g_hwndSongList, 1, &lvc);
// 比特率列
lvc.fmt = LVCFMT_RIGHT;
lvc.cx = 120;
lvc.pszText = const_cast<LPWSTR>(L"比特率");
ListView_InsertColumn(g_hwndSongList, 2, &lvc);
// 时长列
lvc.fmt = LVCFMT_RIGHT;
lvc.cx = 110;
lvc.pszText = const_cast<LPWSTR>(L"时长");
ListView_InsertColumn(g_hwndSongList, 3, &lvc);
// 设置字体
SendMessage(hPrevBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(hPlayBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(hNextBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(g_hwndModeButton, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(hAddFileBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(hAddFolderBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(hClearListBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(g_hwndTitleLabel, WM_SETFONT, (WPARAM)hLargeFont, TRUE);
SendMessage(g_hwndProgressText, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(hSearchEdit, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(hSearchBtn, WM_SETFONT, (WPARAM)hFont, TRUE);
SendMessage(g_hwndSongList, WM_SETFONT, (WPARAM)hFont, TRUE);
}
LRESULT CALLBACK WndProc(HWND hWnd, UINT message, WPARAM wParam, LPARAM lParam)
{
switch (message)
{
case WM_COMMAND:
{
int wmId = LOWORD(wParam);
switch (wmId)
{
case IDM_ABOUT:
DialogBox(hInst, MAKEINTRESOURCE(IDD_ABOUTBOX), hWnd, About);
break;
case IDM_EXIT:
SavePlaylist(); // 确保退出时保存
DestroyWindow(hWnd);
break;
case IDC_MODE_BUTTON:
{
g_currentMode = (g_currentMode == PlayMode::AllLoop) ? PlayMode::SingleLoop :
(g_currentMode == PlayMode::SingleLoop) ? PlayMode::Random : PlayMode::AllLoop;
UpdateModeButtonText();
const WCHAR* modeText = L"";
switch (g_currentMode)
{
case PlayMode::AllLoop:
modeText = L"全部循环";
break;
case PlayMode::SingleLoop:
modeText = L"单曲循环";
break;
case PlayMode::Random:
modeText = L"随机播放";
break;
}
WCHAR tip[SMALL_BUFFER_SIZE];
swprintf_s(tip, SMALL_BUFFER_SIZE, L"当前模式:%s", modeText);
SetWindowText(g_hwndTitleLabel, tip);
SetTimer(hWnd, 1, 1000, [](HWND hwnd, UINT uMsg, UINT_PTR idEvent, DWORD dwTime) {
if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size()) {
const SongInfo& song = g_songList[g_currentSongIndex];
WCHAR fileName[MAX_PATH] = { 0 };
wcscpy_s(fileName, PathFindFileNameW(song.filePath.c_str()));
WCHAR folderPath[MAX_PATH] = { 0 };
wcscpy_s(folderPath, song.filePath.c_str());
PathRemoveFileSpecW(folderPath);
WCHAR fullTitle[LARGE_BUFFER_SIZE] = { 0 };
swprintf_s(fullTitle, L"%s (%s)", fileName, folderPath);
SetWindowText(g_hwndTitleLabel, fullTitle);
}
KillTimer(hwnd, idEvent);
});
break;
}
case IDC_CLEAR_LIST_BUTTON:
if (g_songList.empty()) {
SetWindowText(g_hwndTitleLabel, L"播放列表已为空");
break;
}
StopPlayback();
ClearSongList();
g_currentSongIndex = -1;
UpdateSongListUI();
SavePlaylist();
SetWindowText(g_hwndTitleLabel, L"播放列表已清空");
break;
case IDC_PLAY_BUTTON:
if (g_songList.empty()) break;
if (g_isPlaying) {
PausePlayback();
SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"▶");
}
else {
if (g_currentSongIndex == -1) g_currentSongIndex = 0;
if (g_currentSongIndex < (int)g_songList.size()) {
ResumePlayback();
SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
}
}
g_isPlaying = !g_isPlaying;
break;
case IDC_PREV_BUTTON:
if (g_songList.empty()) break;
StopPlayback();
g_currentSongIndex = GetPrevIndex();
PlaySong(g_songList[g_currentSongIndex]);
g_isPlaying = TRUE;
SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
UpdateSongListUI();
break;
case IDC_NEXT_BUTTON:
if (g_songList.empty()) break;
StopPlayback();
g_currentSongIndex = GetNextIndex();
PlaySong(g_songList[g_currentSongIndex]);
g_isPlaying = TRUE;
SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
UpdateSongListUI();
break;
case IDC_ADD_FILE_BUTTON:
AddFiles(hWnd);
break;
case IDC_ADD_FOLDER_BUTTON:
AddFolder(hWnd);
break;
case IDC_SEARCH_BUTTON:
{
if (g_songList.empty()) break;
WCHAR searchText[MEDIUM_BUFFER_SIZE] = { 0 };
GetDlgItemTextW(hWnd, IDC_SEARCH_EDIT, searchText, MEDIUM_BUFFER_SIZE);
if (wcslen(searchText) == 0) {
MessageBoxW(hWnd, L"请输入搜索内容", L"提示", MB_OK | MB_ICONINFORMATION);
break;
}
std::wstring searchStr(searchText);
std::transform(searchStr.begin(), searchStr.end(), searchStr.begin(), ::towlower);
int foundIndex = -1;
for (size_t i = 0; i < g_songList.size(); ++i) {
std::wstring fileName = g_songList[i].fileName;
std::transform(fileName.begin(), fileName.end(), fileName.begin(), ::towlower);
if (fileName.find(searchStr) != std::wstring::npos) {
foundIndex = static_cast<int>(i);
break;
}
}
if (foundIndex >= 0) {
ListView_SetItemState(g_hwndSongList, foundIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
ListView_EnsureVisible(g_hwndSongList, foundIndex, FALSE);
WCHAR prompt[LARGE_BUFFER_SIZE];
swprintf_s(prompt, LARGE_BUFFER_SIZE, L"找到歌曲: %s\n是否立即播放?", g_songList[foundIndex].fileName.c_str());
if (MessageBoxW(hWnd, prompt, L"找到歌曲", MB_YESNO | MB_ICONQUESTION) == IDYES) {
StopPlayback();
g_currentSongIndex = foundIndex;
PlaySong(g_songList[foundIndex]);
g_isPlaying = TRUE;
SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
}
}
else {
MessageBoxW(hWnd, L"未找到匹配的歌曲", L"提示", MB_OK | MB_ICONINFORMATION);
}
break;
}
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
}
break;
case WM_NOTIFY:
{
// 处理ListView通知消息
LPNMHDR pNMHDR = (LPNMHDR)lParam;
if (pNMHDR->hwndFrom == g_hwndSongList && pNMHDR->code == NM_DBLCLK) {
// 双击列表项
LPNMITEMACTIVATE pNMIA = (LPNMITEMACTIVATE)lParam;
if (pNMIA->iItem != -1) {
StopPlayback();
g_currentSongIndex = pNMIA->iItem;
PlaySong(g_songList[g_currentSongIndex]);
g_isPlaying = TRUE;
SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
UpdateSongListUI();
}
}
break;
}
case WM_USER_PLAY_COMPLETE:
{
g_isPlaying = FALSE;
SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"▶");
if (g_songList.empty()) {
SetWindowText(g_hwndTitleLabel, L"播放列表为空");
break;
}
int nextIndex = GetNextIndex();
if (nextIndex < 0 || nextIndex >= (int)g_songList.size()) {
nextIndex = 0;
}
StopPlayback();
if (PathFileExistsW(g_songList[nextIndex].filePath.c_str())) {
g_currentSongIndex = nextIndex;
PlaySong(g_songList[nextIndex]);
g_isPlaying = TRUE;
SetWindowText(GetDlgItem(hWnd, IDC_PLAY_BUTTON), L"||");
UpdateSongListUI();
}
else {
SetWindowText(g_hwndTitleLabel, L"下一首文件不存在");
}
}
break;
case WM_HSCROLL:
{
HWND hSlider = (HWND)lParam;
// 1. 音量滑块
if (hSlider == GetDlgItem(hWnd, IDC_VOLUME_SLIDER))
{
LRESULT volume = SendMessage(hSlider, TBM_GETPOS, 0, 0);
SetVolume((int)volume);
break;
}
// 进度条拖动结束:从拖动位置继续播放
else if (hSlider == g_hwndProgressBar)
{
int code = LOWORD(wParam);
if (code == TB_ENDTRACK)
{
// 1. 计算目标跳转时间(秒)—— 保留原有逻辑
int pos = (int)SendMessage(g_hwndProgressBar, TBM_GETPOS, 0, 0);
double seekTime = (double)pos / 1000.0 * g_totalDuration;
if (seekTime < 0) seekTime = 0;
if (seekTime > g_totalDuration) seekTime = g_totalDuration;
// 2. 停止当前解码与播放 —— 保留原有逻辑
StopPlayback();
// 3. 重新打开文件 —— 保留原有逻辑(仅替换后续执行体)
if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size())
{
const SongInfo& song = g_songList[g_currentSongIndex];
double duration = 0.0; // 用于接收OpenAudioFile返回的总时长
if (OpenAudioFile(song.filePath, duration))
{
// 【修复1:将打开文件返回的总时长同步到全局变量】
if (duration > 0.0) {
g_totalDuration = duration;
}
// 【修复2:更稳妥的跳转逻辑(优先按音频流TimeBase计算,兜底兼容)】
if (fmt_ctx && audio_stream_idx >= 0 && seekTime > 0.0)
{
AVStream* st = fmt_ctx->streams[audio_stream_idx];
if (st && dec_ctx) {
// 按音频流的时间基准计算目标位置(更精准)
int64_t seek_pts = (int64_t)(seekTime / av_q2d(st->time_base));
avcodec_flush_buffers(dec_ctx); // 清空解码器缓冲(避免旧数据残留)
av_seek_frame(fmt_ctx, audio_stream_idx, seek_pts, AVSEEK_FLAG_BACKWARD);
}
else if (fmt_ctx)
{
// 兜底方案:按全局时间基准跳转(兼容旧逻辑)
int64_t seek_ts = (int64_t)(seekTime * AV_TIME_BASE);
avformat_seek_file(fmt_ctx, -1, INT64_MIN, seek_ts, INT64_MAX, 0);
}
}
// 【修复3:同步跳转后的播放状态】
g_playedDuration = seekTime; // 已播放时长设为跳转目标
g_lastUpdateTime = GetCurrentTimeSeconds(); // 重置进度更新计时起点
// 【修复4:重启解码线程与播放流程】
g_stopDecode = false;
is_playing_ffmpeg = true;
g_decodeThread = std::thread(DecodeThreadFunc);
// 【修复5:恢复UI与播放状态】
g_isPlaying = TRUE;
SetWindowText(GetDlgItem(g_hwndMain, IDC_PLAY_BUTTON), L"||"); // 按钮切为“暂停”
SetTimer(g_hwndMain, PROGRESS_TIMER_ID, PROGRESS_UPDATE_INTERVAL, NULL); // 重启进度更新
UpdateProgressDisplay(); // 立即刷新进度条与时间显示
}
}
}
}
break;
}
case WM_TIMER:
{
if (wParam == PROGRESS_TIMER_ID) // 进度更新专用定时器
{
UpdateProgressDisplay();
}
break;
}
case WM_USER_UPDATE_PROGRESS:
{
UpdateProgressDisplay();
break;
}
case WM_PAINT:
{
PAINTSTRUCT ps;
HDC hdc = BeginPaint(hWnd, &ps);
EndPaint(hWnd, &ps);
}
break;
case WM_DESTROY:
SavePlaylist(); // 确保窗口销毁时保存
PostQuitMessage(0);
break;
default:
return DefWindowProc(hWnd, message, wParam, lParam);
}
return 0;
}
INT_PTR CALLBACK About(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam)
{
UNREFERENCED_PARAMETER(lParam);
switch (message)
{
case WM_INITDIALOG:
return (INT_PTR)TRUE;
case WM_COMMAND:
if (LOWORD(wParam) == IDOK || LOWORD(wParam) == IDCANCEL)
{
EndDialog(hDlg, LOWORD(wParam));
return (INT_PTR)TRUE;
}
break;
}
return (INT_PTR)FALSE;
}
// 获取当前时间(秒)- 用于进度计算
double GetCurrentTimeSeconds()
{
return (double)timeGetTime() / 1000.0;
}
// 格式化时长为分:秒格式
std::wstring FormatDuration(double seconds)
{
int totalSeconds = static_cast<int>(seconds);
int minutes = totalSeconds / 60;
int secs = totalSeconds % 60;
WCHAR buf[SMALL_BUFFER_SIZE];
swprintf_s(buf, SMALL_BUFFER_SIZE, L"%02d:%02d", minutes, secs);
return buf;
}
// 在全局变量区域添加以下变量用于优化
double g_lastUpdatedTime = -1.0; // 记录上次更新标题栏的时间
std::wstring g_lastTitleText; // 缓存上次标题文本
// 修改后的UpdateProgressDisplay函数
void UpdateProgressDisplay()
{
if (!g_hwndMain || !g_hwndProgressBar || !g_hwndProgressText)
return;
// 防止拖动时自动更新滑块
if (GetKeyState(VK_LBUTTON) < 0)
return;
// 仅在播放中且总时长有效时更新
if (is_playing_ffmpeg && g_totalDuration > 0.0)
{
// 计算自上次更新以来的时间差,并更新已播放时长
double currentTime = GetCurrentTimeSeconds();
double timeDiff = currentTime - g_lastUpdateTime;
if (timeDiff > 0)
{
// 使用原子操作安全地更新已播放时长
double oldValue = g_playedDuration;
double newValue = oldValue + (timeDiff * g_playbackSpeed);
// 确保不会超过总时长
if (newValue > g_totalDuration)
newValue = g_totalDuration;
g_playedDuration = newValue;
g_lastUpdateTime = currentTime;
}
// 1. 进度条更新(保持高频但轻量)
int pos = (int)((g_playedDuration / g_totalDuration) * 1000);
SendMessage(g_hwndProgressBar, TBM_SETPOS, TRUE, pos);
// 2. 进度文本更新(仅当时间变化时更新)
std::wstring currTime = FormatDuration(g_playedDuration);
std::wstring totalTime = FormatDuration(g_totalDuration);
// 只在时间显示变化时才更新文本(减少80%的更新次数)
static std::wstring lastTimeText;
WCHAR timeText[SMALL_BUFFER_SIZE];
swprintf_s(timeText, SMALL_BUFFER_SIZE, L"%s / %s", currTime.c_str(), totalTime.c_str());
if (timeText != lastTimeText)
{
SetWindowText(g_hwndProgressText, timeText);
lastTimeText = timeText;
}
// 3. 标题栏更新(关键优化:降低更新频率)
if (g_hwndTitleLabel)
{
// 仅每秒更新一次标题栏(大幅减少重绘)
if (g_playedDuration - g_lastUpdatedTime >= 1.0 || g_lastUpdatedTime < 0)
{
g_lastUpdatedTime = g_playedDuration;
WCHAR originalTitle[LARGE_BUFFER_SIZE];
GetWindowText(g_hwndTitleLabel, originalTitle, LARGE_BUFFER_SIZE);
// 移除之前的时间信息
WCHAR* pipePos = wcschr(originalTitle, L'|');
std::wstring baseTitle = (pipePos) ? std::wstring(originalTitle, pipePos) : std::wstring(originalTitle);
// 构建新标题
WCHAR fullTitle[LARGE_BUFFER_SIZE];
swprintf_s(fullTitle, LARGE_BUFFER_SIZE, L"%s | %s / %s",
baseTitle.c_str(), currTime.c_str(), totalTime.c_str());
// 仅在内容变化时更新(避免相同文本重复设置)
if (fullTitle != g_lastTitleText)
{
SetWindowText(g_hwndTitleLabel, fullTitle);
g_lastTitleText = fullTitle;
}
}
}
}
}
void UpdateModeButtonText()
{
if (!g_hwndModeButton) return;
const WCHAR* modeText = L"";
switch (g_currentMode)
{
case PlayMode::AllLoop:
modeText = L"全部循环";
break;
case PlayMode::SingleLoop:
modeText = L"单曲循环";
break;
case PlayMode::Random:
modeText = L"随机播放";
break;
}
SetWindowText(g_hwndModeButton, modeText);
}
int GetNextIndex()
{
if (g_songList.empty()) return -1;
int count = (int)g_songList.size();
if (count == 1) return 0;
switch (g_currentMode)
{
case PlayMode::SingleLoop:
return g_currentSongIndex;
case PlayMode::Random:
{
std::uniform_int_distribution<int> dist(0, count - 1);
int idx = dist(g_randEngine);
int tries = 0;
while (idx == g_currentSongIndex && tries++ < 10) {
idx = dist(g_randEngine);
}
return idx;
}
case PlayMode::AllLoop:
default:
if (g_currentSongIndex < 0) return 0;
return (g_currentSongIndex + 1) % count;
}
}
int GetPrevIndex()
{
if (g_songList.empty()) return -1;
int count = (int)g_songList.size();
if (count == 1) return 0;
switch (g_currentMode)
{
case PlayMode::SingleLoop:
return g_currentSongIndex;
case PlayMode::Random:
{
std::uniform_int_distribution<int> dist(0, count - 1);
int idx = dist(g_randEngine);
int tries = 0;
while (idx == g_currentSongIndex && tries++ < 10) {
idx = dist(g_randEngine);
}
return idx;
}
case PlayMode::AllLoop:
default:
if (g_currentSongIndex <= 0) return count - 1;
return (g_currentSongIndex - 1 + count) % count;
}
}
void InitFFmpeg()
{
avformat_network_init();
}
std::string WstringToUtf8(const std::wstring& wstr)
{
if (wstr.empty()) return "";
int size_needed = WideCharToMultiByte(CP_UTF8, 0,
wstr.data(), (int)wstr.size(), NULL, 0, NULL, NULL);
if (size_needed == 0) return "";
std::string str(size_needed, 0);
WideCharToMultiByte(CP_UTF8, 0,
wstr.data(), (int)wstr.size(), &str[0], size_needed, NULL, NULL);
return str;
}
bool OpenAudioFile(const std::wstring& filePath, double& duration)
{
CloseAudioFile(); // 先确保旧资源已释放
std::string utf8_path = WstringToUtf8(filePath);
if (utf8_path.empty()) return false;
if (avformat_open_input(&fmt_ctx, utf8_path.c_str(), NULL, NULL) < 0) {
SetWindowText(g_hwndTitleLabel, L"错误:无法打开音频文件");
return false;
}
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
SetWindowText(g_hwndTitleLabel, L"错误:无法获取流信息");
avformat_close_input(&fmt_ctx);
fmt_ctx = NULL;
return false;
}
audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
duration = 0.0;
if (audio_stream_idx >= 0) {
AVStream* audio_stream = fmt_ctx->streams[audio_stream_idx];
if (audio_stream->duration != AV_NOPTS_VALUE) {
duration = (double)audio_stream->duration * av_q2d(audio_stream->time_base);
}
else if (fmt_ctx->duration != AV_NOPTS_VALUE) {
duration = (double)fmt_ctx->duration / AV_TIME_BASE;
}
}
if (audio_stream_idx < 0) {
SetWindowText(g_hwndTitleLabel, L"错误:文件中无音频流");
avformat_close_input(&fmt_ctx);
fmt_ctx = NULL;
return false;
}
AVCodecParameters* codec_par = fmt_ctx->streams[audio_stream_idx]->codecpar;
const AVCodec* codec = avcodec_find_decoder(codec_par->codec_id);
if (!codec) {
SetWindowText(g_hwndTitleLabel, L"错误:不支持的音频编码");
avformat_close_input(&fmt_ctx);
fmt_ctx = NULL;
return false;
}
dec_ctx = avcodec_alloc_context3(codec);
if (!dec_ctx) {
SetWindowText(g_hwndTitleLabel, L"错误:无法分配解码器上下文");
avformat_close_input(&fmt_ctx);
fmt_ctx = NULL;
return false;
}
if (avcodec_parameters_to_context(dec_ctx, codec_par) < 0) {
SetWindowText(g_hwndTitleLabel, L"错误:无法复制解码器参数");
avcodec_free_context(&dec_ctx);
dec_ctx = NULL;
avformat_close_input(&fmt_ctx);
fmt_ctx = NULL;
return false;
}
if (avcodec_open2(dec_ctx, codec, NULL) < 0) {
SetWindowText(g_hwndTitleLabel, L"错误:无法打开解码器");
avcodec_free_context(&dec_ctx);
dec_ctx = NULL;
avformat_close_input(&fmt_ctx);
fmt_ctx = NULL;
return false;
}
swr_ctx = swr_alloc_set_opts(
NULL,
av_get_default_channel_layout(dec_ctx->channels), // 目标声道布局(与原始一致)
dec_ctx->sample_fmt, // 目标采样格式(与原始一致)
dec_ctx->sample_rate, // 目标采样率(与原始一致)
av_get_default_channel_layout(dec_ctx->channels), // 原始声道布局
dec_ctx->sample_fmt, // 原始采样格式
dec_ctx->sample_rate, // 原始采样率
0, NULL
);
// 关键修复:初始化SwrContext并检查错误
if (!swr_ctx || swr_init(swr_ctx) < 0) {
SetWindowText(g_hwndTitleLabel, L"错误:重采样上下文初始化失败");
// 释放资源
swr_free(&swr_ctx);
avcodec_free_context(&dec_ctx);
avformat_close_input(&fmt_ctx);
return false;
}
pkt = av_packet_alloc();
frame = av_frame_alloc();
if (!pkt || !frame) {
SetWindowText(g_hwndTitleLabel, L"错误:无法分配缓存");
if (pkt) {
av_packet_free(&pkt);
pkt = NULL;
}
if (frame) {
av_frame_free(&frame);
frame = NULL;
}
if (swr_ctx) {
swr_free(&swr_ctx);
swr_ctx = NULL;
}
avcodec_free_context(&dec_ctx);
dec_ctx = NULL;
avformat_close_input(&fmt_ctx);
fmt_ctx = NULL;
return false;
}
// 1. 定义WaveOut支持的目标格式(16位整数PCM)
AVSampleFormat target_sample_fmt = AV_SAMPLE_FMT_S16; // 16位整数(WaveOut必支持)
int target_channels = dec_ctx->channels; // 保留原始声道数
int target_sample_rate = dec_ctx->sample_rate; // 保留原始采样率
// 2. 重新配置SwrContext(输入:原始格式,输出:16位PCM)
swr_ctx = swr_alloc_set_opts(
NULL,
av_get_default_channel_layout(target_channels), // 目标声道布局
target_sample_fmt, // 目标采样格式(16位整数)
target_sample_rate, // 目标采样率
av_get_default_channel_layout(dec_ctx->channels),// 原始声道布局
dec_ctx->sample_fmt, // 原始采样格式
dec_ctx->sample_rate, // 原始采样率
0, NULL
);
// 3. 初始化SwrContext
if (!swr_ctx || swr_init(swr_ctx) < 0) {
SetWindowText(g_hwndTitleLabel, L"错误:重采样上下文初始化失败");
swr_free(&swr_ctx);
avcodec_free_context(&dec_ctx);
avformat_close_input(&fmt_ctx);
return false;
}
// 4. 配置WAVEFORMATEX(匹配16位PCM格式)
WAVEFORMATEX wfx = { 0 };
wfx.wFormatTag = WAVE_FORMAT_PCM; // 标准PCM格式
wfx.nChannels = target_channels; // 原始声道数
wfx.nSamplesPerSec = target_sample_rate; // 原始采样率
wfx.wBitsPerSample = av_get_bytes_per_sample(target_sample_fmt) * 8; // 16位
wfx.nBlockAlign = (wfx.wBitsPerSample / 8) * wfx.nChannels; // 块对齐(必须正确)
wfx.nAvgBytesPerSec = wfx.nSamplesPerSec * wfx.nBlockAlign; // 每秒字节数
wfx.cbSize = 0; // 标准PCM格式,cbSize设为0
// 5. 检查waveOutOpen是否成功(添加错误日志)
MMRESULT waveResult = waveOutOpen(
&hWaveOut,
WAVE_MAPPER,
&wfx,
(DWORD_PTR)WaveOutProc,
(DWORD_PTR)NULL,
CALLBACK_FUNCTION
);
if (waveResult != MMSYSERR_NOERROR) {
WCHAR errMsg[256];
swprintf_s(errMsg, L"错误:无法打开音频设备(代码:%d)", waveResult);
SetWindowText(g_hwndTitleLabel, errMsg);
CloseAudioFile();
return false;
}
g_buffers.clear();
g_buffers.resize(N_BUFFERS);
{
std::lock_guard<std::mutex> lk(g_bufMutex);
while (!g_freeBuffers.empty()) g_freeBuffers.pop();
for (int i = 0; i < N_BUFFERS; ++i) {
g_buffers[i].data.clear();
g_buffers[i].hdr = WAVEHDR();
g_buffers[i].prepared = false;
g_buffers[i].duration = 0.0;
g_freeBuffers.push(i);
}
}
return true;
}
void CloseAudioFile()
{
// 1. 停止解码线程
g_stopDecode = true;
g_bufCv.notify_all();
if (g_decodeThread.joinable()) {
g_decodeThread.join();
}
// 2. 重置音频设备与缓冲区
if (hWaveOut) {
waveOutReset(hWaveOut);
std::lock_guard<std::mutex> lk(g_bufMutex);
for (auto& buf : g_buffers) {
if (buf.prepared) {
waveOutUnprepareHeader(hWaveOut, &buf.hdr, sizeof(WAVEHDR));
buf.prepared = false;
}
buf.data.clear();
buf.hdr = {};
buf.duration = 0.0;
g_freeBuffers.push(&buf - &g_buffers[0]);
}
}
// 3. 释放FFmpeg资源
if (swr_ctx) {
swr_free(&swr_ctx);
swr_ctx = NULL;
}
if (dec_ctx) {
avcodec_free_context(&dec_ctx);
dec_ctx = NULL;
}
if (fmt_ctx) {
avformat_close_input(&fmt_ctx);
fmt_ctx = NULL;
}
if (pkt) {
av_packet_free(&pkt);
pkt = NULL;
}
if (frame) {
av_frame_free(&frame);
frame = NULL;
}
hWaveOut = NULL;
// 重置播放状态
g_totalDuration = 0.0;
g_playedDuration = 0.0;
is_playing_ffmpeg = false;
g_isPlaying = FALSE;
}
bool DecodeAudioFrame(std::vector<uint8_t>& pcm, double& frameDuration)
{
pcm.clear();
frameDuration = 0.0;
int ret = 0;
if (!fmt_ctx || !pkt || !dec_ctx || !frame || !swr_ctx) {
OutputDebugString(L"DecodeAudioFrame:FFmpeg核心指针为空,终止解码\n");
return false;
}
while (true) {
ret = av_read_frame(fmt_ctx, pkt);
if (ret < 0) {
if (ret == AVERROR_EOF) return false;
OutputDebugString(L"警告:读取数据包失败,重试...\n");
if (pkt) av_packet_unref(pkt);
Sleep(10);
continue;
}
if (!pkt) continue;
if (pkt->stream_index != audio_stream_idx) {
av_packet_unref(pkt);
continue;
}
ret = avcodec_send_packet(dec_ctx, pkt);
if (ret < 0) {
if (ret == AVERROR(EAGAIN) || ret == AVERROR_EOF) {
av_packet_unref(pkt);
continue;
}
else {
OutputDebugString(L"警告:发送数据包失败,跳过...\n");
av_packet_unref(pkt);
continue;
}
}
ret = avcodec_receive_frame(dec_ctx, frame);
av_packet_unref(pkt);
if (ret == AVERROR(EAGAIN)) {
continue;
}
else if (ret == AVERROR_EOF) {
return false;
}
else if (ret < 0) {
OutputDebugString(L"警告:接收帧失败,跳过...\n");
continue;
}
// 计算当前帧的时长(秒)
frameDuration = (double)frame->nb_samples / dec_ctx->sample_rate;
// 适配原始采样率:用目标采样率(原始采样率)替换硬编码的44100
int64_t delay = swr_get_delay(swr_ctx, frame->sample_rate);
// 关键修改:用原始采样率(目标采样率)替换44100LL,保持与重采样器配置一致
int64_t out_nb_samples_64 = av_rescale_rnd(
delay + frame->nb_samples,
(int64_t)dec_ctx->sample_rate, // 目标采样率=原始采样率(与重采样器匹配)
frame->sample_rate,
AV_ROUND_UP
);
if (out_nb_samples_64 > INT_MAX) {
av_frame_unref(frame);
return false;
}
int out_nb_samples = (int)out_nb_samples_64;
uint8_t* out_buf[2] = { NULL };
int out_linesize;
if (av_samples_alloc(out_buf, &out_linesize, 2, out_nb_samples, AV_SAMPLE_FMT_S16, 0) < 0 || !out_buf[0]) {
av_frame_unref(frame);
return false;
}
int out_nb_samples_gen = swr_convert(
swr_ctx, out_buf, out_nb_samples,
(const uint8_t**)frame->data, frame->nb_samples
);
// 输出重采样后的样本数(判断是否成功生成数据)
WCHAR swrLog[256];
swprintf_s(swrLog, L"swr_convert 输出样本数: %d(负数表示失败)\n", out_nb_samples_gen);
OutputDebugStringW(swrLog);
// 若重采样失败,添加详细错误日志
if (out_nb_samples_gen < 0) {
swprintf_s(swrLog, L"重采样失败!错误码: %d\n", out_nb_samples_gen);
OutputDebugStringW(swrLog);
}
// 1. 获取目标格式的参数(16位PCM,已在OpenAudioFile中定义)
AVSampleFormat target_sample_fmt = AV_SAMPLE_FMT_S16;
int target_channels = dec_ctx->channels;
// 2. 动态计算PCM数据大小(样本数 × 声道数 × 每个样本的字节数)
int bytes_per_sample = av_get_bytes_per_sample(target_sample_fmt); // 16位=2字节
int out_size = out_nb_samples_gen * target_channels * bytes_per_sample;
// 3. 拷贝数据(确保大小匹配)
if (out_nb_samples_gen > 0 && out_buf[0] != nullptr && out_size > 0) {
pcm.resize(out_size);
memcpy(pcm.data(), out_buf[0], out_size);
}
av_freep(&out_buf[0]);
av_frame_unref(frame);
// 输出函数最终返回状态(成功/失败)
WCHAR retLog[256];
if (!pcm.empty()) {
swprintf_s(retLog, L"DecodeAudioFrame 成功,返回PCM数据大小: %d 字节\n", (int)pcm.size());
}
else {
swprintf_s(retLog, L"DecodeAudioFrame 失败,返回空数据\n");
}
OutputDebugStringW(retLog);
return !pcm.empty();
}
}
void PlayPCMData(const std::vector<uint8_t>& pcm, double duration, int bufIndex)
{
std::lock_guard<std::mutex> lock(g_bufMutex);
if (bufIndex < 0 || bufIndex >= (int)g_buffers.size()) return;
auto& buffer = g_buffers[bufIndex];
buffer.data = pcm; // 直接使用原始数据
buffer.duration = duration;
// 准备WAVEHDR
buffer.hdr.lpData = (LPSTR)buffer.data.data();
buffer.hdr.dwBufferLength = (DWORD)buffer.data.size();
buffer.hdr.dwFlags = 0;
waveOutPrepareHeader(hWaveOut, &buffer.hdr, sizeof(WAVEHDR));
waveOutWrite(hWaveOut, &buffer.hdr, sizeof(WAVEHDR));
buffer.prepared = true;
}
void PlaySong(const SongInfo& song)
{
StopPlayback();
double duration = 0.0;
if (!OpenAudioFile(song.filePath, duration)) {
OutputDebugString(L"错误:打开音频文件失败\n");
CloseAudioFile();
return;
}
if (!hWaveOut || !fmt_ctx || !dec_ctx) {
OutputDebugString(L"错误:音频组件初始化失败\n");
CloseAudioFile();
return;
}
// 初始化进度相关变量
g_totalDuration = duration;
g_playedDuration = 0.0;
g_lastUpdateTime = GetCurrentTimeSeconds();
g_playbackSpeed = 1.0;
g_stopDecode = false;
is_playing_ffmpeg = true;
g_decodeThread = std::thread(DecodeThreadFunc);
// 提取文件名和文件夹路径并拼接显示
WCHAR fileName[MAX_PATH] = { 0 };
wcscpy_s(fileName, MAX_PATH, song.fileName.c_str());
WCHAR folderPath[MAX_PATH] = { 0 };
wcscpy_s(folderPath, MAX_PATH, song.filePath.c_str());
PathRemoveFileSpecW(folderPath);
WCHAR fullTitle[LARGE_BUFFER_SIZE] = { 0 };
swprintf_s(fullTitle, LARGE_BUFFER_SIZE, L"%s (%s) - %s - %s", fileName, folderPath, song.fileSize.c_str(), song.bitrate.c_str());
SetWindowText(g_hwndTitleLabel, fullTitle);
// 初始化进度显示
UpdateProgressDisplay();
// 启动进度条更新定时器
SetTimer(g_hwndMain, PROGRESS_TIMER_ID, PROGRESS_UPDATE_INTERVAL, NULL);
}
void StopPlayback()
{
is_playing_ffmpeg = false;
g_stopDecode = true;
g_bufCv.notify_all();
if (g_decodeThread.joinable()) {
g_decodeThread.join();
}
CloseAudioFile();
// 停止播放时,销毁进度条更新定时器并重置进度条
KillTimer(g_hwndMain, PROGRESS_TIMER_ID);
SendMessage(g_hwndProgressBar, TBM_SETPOS, TRUE, 0);
SetWindowText(g_hwndProgressText, L"00:00 / 00:00");
SetWindowText(g_hwndTitleLabel, L"歌曲播放中");
}
void PausePlayback()
{
if (hWaveOut) {
waveOutPause(hWaveOut);
is_playing_ffmpeg = false;
// 暂停时计算当前已播放时间
double currentTime = GetCurrentTimeSeconds();
double timeDiff = currentTime - g_lastUpdateTime;
if (timeDiff > 0)
{
g_playedDuration += timeDiff * g_playbackSpeed;
g_lastUpdateTime = currentTime;
}
// 暂停时销毁定时器
KillTimer(g_hwndMain, PROGRESS_TIMER_ID);
// 暂停时更新进度显示
UpdateProgressDisplay();
// 暂停时显示详细信息
if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size()) {
const SongInfo& song = g_songList[g_currentSongIndex];
WCHAR fileName[MAX_PATH] = { 0 };
wcscpy_s(fileName, song.fileName.c_str());
WCHAR folderPath[MAX_PATH] = { 0 };
wcscpy_s(folderPath, song.filePath.c_str());
PathRemoveFileSpecW(folderPath);
// 显示当前进度
std::wstring currTime = FormatDuration(g_playedDuration);
std::wstring totalTime = FormatDuration(g_totalDuration);
WCHAR fullTitle[LARGE_BUFFER_SIZE] = { 0 };
swprintf_s(fullTitle, LARGE_BUFFER_SIZE, L"已暂停 - %s (%s) | %s / %s",
fileName, folderPath, currTime.c_str(), totalTime.c_str());
SetWindowText(g_hwndTitleLabel, fullTitle);
}
else {
SetWindowText(g_hwndTitleLabel, L"已暂停 - 请选择歌曲播放");
}
}
}
void ResumePlayback()
{
if (hWaveOut) {
waveOutRestart(hWaveOut);
is_playing_ffmpeg = true;
// 更新上次更新时间为当前时间
g_lastUpdateTime = GetCurrentTimeSeconds();
// 继续播放时重启定时器
SetTimer(g_hwndMain, PROGRESS_TIMER_ID, PROGRESS_UPDATE_INTERVAL, NULL);
if (!g_decodeThread.joinable()) {
g_stopDecode = false;
g_decodeThread = std::thread(DecodeThreadFunc);
}
// 继续播放时显示完整信息
if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size()) {
const SongInfo& song = g_songList[g_currentSongIndex];
WCHAR fileName[MAX_PATH] = { 0 };
wcscpy_s(fileName, song.fileName.c_str());
WCHAR folderPath[MAX_PATH] = { 0 };
wcscpy_s(folderPath, song.filePath.c_str());
PathRemoveFileSpecW(folderPath);
WCHAR fullTitle[LARGE_BUFFER_SIZE] = { 0 };
swprintf_s(fullTitle, LARGE_BUFFER_SIZE, L"%s (%s) - %s - %s", fileName, folderPath, song.fileSize.c_str(), song.bitrate.c_str());
SetWindowText(g_hwndTitleLabel, fullTitle);
}
}
else {
if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size()) {
PlaySong(g_songList[g_currentSongIndex]);
}
}
}
void DecodeThreadFunc()
{
std::vector<uint8_t> pcm;
double frameDuration = 0.0;
bool isFirstFrame = true;
bool decode_finished = false;
while (!g_stopDecode && !decode_finished) {
if (!is_playing_ffmpeg) {
std::this_thread::sleep_for(std::chrono::milliseconds(50));
continue;
}
if (g_totalDuration > 0.0 && g_playedDuration >= g_totalDuration)
{
decode_finished = true;
break;
}
pcm.clear();
frameDuration = 0.0;
bool decodeSuccess = DecodeAudioFrame(pcm, frameDuration);
if (!decodeSuccess) {
decode_finished = true;
break;
}
while (pcm.size() < MIN_BUFFER_SIZE && pcm.size() < MAX_BUFFER_SIZE && !g_stopDecode && is_playing_ffmpeg) {
std::vector<uint8_t> nextPcm;
double nextDuration = 0.0;
if (!DecodeAudioFrame(nextPcm, nextDuration) || nextPcm.empty()) break;
if (pcm.size() + nextPcm.size() > MAX_BUFFER_SIZE) break;
pcm.insert(pcm.end(), nextPcm.begin(), nextPcm.end());
frameDuration += nextDuration;
}
if (pcm.empty()) {
std::this_thread::sleep_for(std::chrono::milliseconds(10));
continue;
}
int freeBufIdx = -1;
{
std::unique_lock<std::mutex> lk(g_bufMutex);
g_bufCv.wait(lk, [] {
return g_stopDecode || !is_playing_ffmpeg || !g_freeBuffers.empty();
});
if (g_stopDecode || !is_playing_ffmpeg) break;
if (g_freeBuffers.empty()) continue;
freeBufIdx = g_freeBuffers.front();
g_freeBuffers.pop();
}
PCMBuffer& freeBuf = g_buffers[freeBufIdx];
freeBuf.data = pcm;
freeBuf.duration = frameDuration;
freeBuf.hdr.lpData = (LPSTR)freeBuf.data.data();
freeBuf.hdr.dwBufferLength = (DWORD)freeBuf.data.size();
freeBuf.hdr.dwFlags = 0;
freeBuf.hdr.dwLoops = 0;
if (!freeBuf.prepared) {
if (waveOutPrepareHeader(hWaveOut, &freeBuf.hdr, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
std::lock_guard<std::mutex> lk(g_bufMutex);
g_freeBuffers.push(freeBufIdx);
OutputDebugString(L"错误:缓冲区准备失败\n");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
continue;
}
freeBuf.prepared = true;
}
if (waveOutWrite(hWaveOut, &freeBuf.hdr, sizeof(WAVEHDR)) != MMSYSERR_NOERROR) {
std::lock_guard<std::mutex> lk(g_bufMutex);
g_freeBuffers.push(freeBufIdx);
OutputDebugString(L"错误:音频数据写入失败\n");
std::this_thread::sleep_for(std::chrono::milliseconds(50));
continue;
}
if (isFirstFrame) {
OutputDebugString(L"首帧播放成功,持续解码中...\n");
isFirstFrame = false;
}
}
// 等待播放完成
while (!g_stopDecode && g_playedDuration < g_totalDuration)
{
std::this_thread::sleep_for(std::chrono::milliseconds(50));
}
if (g_hwndMain && !g_stopDecode) {
PostMessage(g_hwndMain, WM_USER_PLAY_COMPLETE, 0, 0);
}
is_playing_ffmpeg = false;
g_stopDecode = true;
std::lock_guard<std::mutex> lk(g_bufMutex);
g_bufCv.notify_all();
OutputDebugString(L"解码线程正常退出\n");
}
void AddFiles(HWND hWnd)
{
OPENFILENAMEW ofn;
WCHAR szFile[MAX_PATH * 20] = { 0 };
ZeroMemory(&ofn, sizeof(ofn));
ofn.lStructSize = sizeof(ofn);
ofn.hwndOwner = hWnd;
ofn.lpstrFile = szFile;
ofn.nMaxFile = sizeof(szFile) / sizeof(WCHAR);
ofn.lpstrFilter = L"音频文件 (*.mp3;*.wav;*.wma;*.flac;*.m4a;*.ogg)\0*.mp3;*.wav;*.wma;*.flac;*.m4a;*.ogg\0所有文件 (*.*)\0*.*\0";
ofn.nFilterIndex = 1;
ofn.lpstrFileTitle = NULL;
ofn.nMaxFileTitle = 0;
ofn.lpstrInitialDir = NULL;
ofn.Flags = OFN_PATHMUSTEXIST | OFN_FILEMUSTEXIST | OFN_ALLOWMULTISELECT | OFN_EXPLORER;
if (GetOpenFileNameW(&ofn) == TRUE) {
WCHAR* p = szFile;
WCHAR path[MAX_PATH] = { 0 };
wcscpy_s(path, MAX_PATH, p);
p += wcslen(p) + 1;
bool added = false;
if (*p == 0) {
// 单个文件
SongInfo song;
song.filePath = path;
song.fileName = PathFindFileNameW(path);
// 获取文件大小
int64_t sizeBytes;
song.fileSize = GetFileSizeString(path, sizeBytes);
song.sizeBytes = sizeBytes;
// 获取比特率
int bitrateKbps;
song.bitrate = GetBitrateString(path, bitrateKbps);
song.bitrateKbps = bitrateKbps;
// 获取时长
std::string utf8_path = WstringToUtf8(path);
AVFormatContext* fmt_ctx = NULL;
if (avformat_open_input(&fmt_ctx, utf8_path.c_str(), NULL, NULL) == 0) {
avformat_find_stream_info(fmt_ctx, NULL);
int audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audio_stream_idx >= 0) {
AVStream* audio_stream = fmt_ctx->streams[audio_stream_idx];
if (audio_stream->duration != AV_NOPTS_VALUE) {
song.duration = (double)audio_stream->duration * av_q2d(audio_stream->time_base);
}
else if (fmt_ctx->duration != AV_NOPTS_VALUE) {
song.duration = (double)fmt_ctx->duration / AV_TIME_BASE;
}
}
avformat_close_input(&fmt_ctx);
}
// 检查是否已存在
bool exists = false;
for (const auto& s : g_songList) {
if (s.filePath == song.filePath) {
exists = true;
break;
}
}
if (!exists) {
g_songList.push_back(song);
added = true;
}
}
else {
// 多个文件
while (*p) {
WCHAR fullPath[MAX_PATH] = { 0 };
wcscpy_s(fullPath, MAX_PATH, path);
wcscat_s(fullPath, MAX_PATH, L"\\");
wcscat_s(fullPath, MAX_PATH, p);
SongInfo song;
song.filePath = fullPath;
song.fileName = PathFindFileNameW(fullPath);
// 获取文件大小
int64_t sizeBytes;
song.fileSize = GetFileSizeString(fullPath, sizeBytes);
song.sizeBytes = sizeBytes;
// 获取比特率
int bitrateKbps;
song.bitrate = GetBitrateString(fullPath, bitrateKbps);
song.bitrateKbps = bitrateKbps;
// 获取时长
std::string utf8_path = WstringToUtf8(fullPath);
AVFormatContext* fmt_ctx = NULL;
if (avformat_open_input(&fmt_ctx, utf8_path.c_str(), NULL, NULL) == 0) {
avformat_find_stream_info(fmt_ctx, NULL);
int audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audio_stream_idx >= 0) {
AVStream* audio_stream = fmt_ctx->streams[audio_stream_idx];
if (audio_stream->duration != AV_NOPTS_VALUE) {
song.duration = (double)audio_stream->duration * av_q2d(audio_stream->time_base);
}
else if (fmt_ctx->duration != AV_NOPTS_VALUE) {
song.duration = (double)fmt_ctx->duration / AV_TIME_BASE;
}
}
avformat_close_input(&fmt_ctx);
}
// 检查是否已存在
bool exists = false;
for (const auto& s : g_songList) {
if (s.filePath == song.filePath) {
exists = true;
break;
}
}
if (!exists) {
g_songList.push_back(song);
added = true;
}
p += wcslen(p) + 1;
}
}
if (added) {
UpdateSongListUI();
SavePlaylist();
}
}
}
void AddFolder(HWND hWnd)
{
WCHAR szFolder[MAX_PATH] = { 0 };
BROWSEINFOW bi = { 0 };
bi.hwndOwner = hWnd;
bi.pszDisplayName = szFolder;
bi.lpszTitle = L"选择音频文件夹";
bi.ulFlags = BIF_RETURNONLYFSDIRS | BIF_NEWDIALOGSTYLE;
LPITEMIDLIST pidl = SHBrowseForFolderW(&bi);
if (pidl != NULL) {
if (SHGetPathFromIDListW(pidl, szFolder)) {
EnumMusicFiles(szFolder);
UpdateSongListUI();
SavePlaylist();
}
CoTaskMemFree(pidl);
}
}
void ClearSongList()
{
g_songList.clear();
StopPlayback();
SetWindowText(g_hwndTitleLabel, L"播放列表已清空");
}
void EnumMusicFiles(const std::wstring& folder)
{
std::wstring searchPath = folder + L"\\*.*";
WIN32_FIND_DATAW findData;
HANDLE hFind = FindFirstFileW(searchPath.c_str(), &findData);
if (hFind == INVALID_HANDLE_VALUE) return;
do {
if (wcscmp(findData.cFileName, L".") == 0 || wcscmp(findData.cFileName, L"..") == 0)
continue;
std::wstring fullPath = folder + L"\\" + findData.cFileName;
if (findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) {
EnumMusicFiles(fullPath); // 递归处理子文件夹
}
else {
WCHAR* ext = PathFindExtensionW(findData.cFileName);
if (ext != NULL) {
if (_wcsicmp(ext, L".mp3") == 0 || _wcsicmp(ext, L".wav") == 0 ||
_wcsicmp(ext, L".wma") == 0 || _wcsicmp(ext, L".flac") == 0 ||
_wcsicmp(ext, L".m4a") == 0 || _wcsicmp(ext, L".ogg") == 0) {
// 检查是否已存在
bool exists = false;
for (const auto& song : g_songList) {
if (song.filePath == fullPath) {
exists = true;
break;
}
}
if (!exists) {
SongInfo song;
song.filePath = fullPath;
song.fileName = PathFindFileNameW(fullPath.c_str());
// 获取文件大小
int64_t sizeBytes;
song.fileSize = GetFileSizeString(fullPath, sizeBytes);
song.sizeBytes = sizeBytes;
// 获取比特率
int bitrateKbps;
song.bitrate = GetBitrateString(fullPath, bitrateKbps);
song.bitrateKbps = bitrateKbps;
// 获取时长
std::string utf8_path = WstringToUtf8(fullPath);
AVFormatContext* fmt_ctx = NULL;
if (avformat_open_input(&fmt_ctx, utf8_path.c_str(), NULL, NULL) == 0) {
avformat_find_stream_info(fmt_ctx, NULL);
int audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
if (audio_stream_idx >= 0) {
AVStream* audio_stream = fmt_ctx->streams[audio_stream_idx];
if (audio_stream->duration != AV_NOPTS_VALUE) {
song.duration = (double)audio_stream->duration * av_q2d(audio_stream->time_base);
}
else if (fmt_ctx->duration != AV_NOPTS_VALUE) {
song.duration = (double)fmt_ctx->duration / AV_TIME_BASE;
}
}
avformat_close_input(&fmt_ctx);
}
g_songList.push_back(song);
}
}
}
}
} while (FindNextFileW(hFind, &findData) != 0);
FindClose(hFind);
}
void UpdateSongListUI()
{
if (g_hwndSongList == NULL) return;
// 清空列表
ListView_DeleteAllItems(g_hwndSongList);
// 添加所有歌曲
for (size_t i = 0; i < g_songList.size(); ++i) {
const SongInfo& song = g_songList[i];
// 添加列表项(第一列)
LVITEM lvi = { 0 };
lvi.mask = LVIF_TEXT;
lvi.iItem = (int)i;
lvi.iSubItem = 0;
lvi.pszText = const_cast<LPWSTR>(song.fileName.c_str());
ListView_InsertItem(g_hwndSongList, &lvi);
// 设置第二列(文件大小)
ListView_SetItemText(g_hwndSongList, (int)i, 1, const_cast<LPWSTR>(song.fileSize.c_str()));
// 设置第三列(比特率)
ListView_SetItemText(g_hwndSongList, (int)i, 2, const_cast<LPWSTR>(song.bitrate.c_str()));
// 设置第四列(时长)
std::wstring durationStr = FormatDuration(song.duration);
ListView_SetItemText(g_hwndSongList, (int)i, 3, const_cast<LPWSTR>(durationStr.c_str()));
}
// 选中当前播放的歌曲
if (g_currentSongIndex >= 0 && g_currentSongIndex < (int)g_songList.size()) {
ListView_SetItemState(g_hwndSongList, g_currentSongIndex, LVIS_SELECTED | LVIS_FOCUSED, LVIS_SELECTED | LVIS_FOCUSED);
ListView_EnsureVisible(g_hwndSongList, g_currentSongIndex, FALSE);
}
}
// -------------------------- CSV存储核心函数 --------------------------
// 辅助函数:宽字符串转UTF-8,并处理CSV特殊字符(双引号、逗号)的转义
std::string WstringToUtf8Escaped(const std::wstring& wstr) {
std::string utf8 = WstringToUtf8(wstr);
std::string escaped;
for (char c : utf8) {
if (c == '"') {
escaped += "\"\""; // 双引号转义为两个双引号
}
else if (c == ',') {
escaped += c;
}
else {
escaped += c;
}
}
return escaped;
}
// 辅助函数:UTF-8字符串转宽字符串
std::wstring Utf8ToWstring(const std::string& utf8) {
if (utf8.empty()) return L"";
int size_needed = MultiByteToWideChar(CP_UTF8, 0, utf8.data(), (int)utf8.size(), NULL, 0);
if (size_needed == 0) return L"";
std::wstring wstr(size_needed, 0);
MultiByteToWideChar(CP_UTF8, 0, utf8.data(), (int)utf8.size(), &wstr[0], size_needed);
return wstr;
}
// 辅助函数:解析CSV行,正确分割被双引号包裹的字段
std::vector<std::string> SplitCsvLine(const std::string& line) {
std::vector<std::string> fields;
std::string current_field;
bool in_quotes = false;
for (char c : line) {
if (c == '"') {
// 处理双引号转义("" -> ")
if (in_quotes && !current_field.empty() && current_field.back() == '"') {
current_field.pop_back();
in_quotes = false;
}
else {
in_quotes = !in_quotes;
}
}
else if (c == ',' && !in_quotes) {
// 仅在未被引号包裹时,逗号才作为分隔符
fields.push_back(current_field);
current_field.clear();
}
else {
current_field += c;
}
}
// 添加最后一个字段
if (!current_field.empty()) {
fields.push_back(current_field);
}
return fields;
}
// 保存播放列表(CSV格式,批量写入)
void SavePlaylist() {
WCHAR appDataPath[MAX_PATH] = { 0 };
if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, appDataPath) != S_OK) {
OutputDebugString(L"错误:无法获取 APPDATA 路径\n");
return;
}
// 1. 创建存储目录
std::wstring folderPath = appDataPath;
folderPath += L"\\MiniMusicPlayer";
if (!CreateDirectoryW(folderPath.c_str(), NULL)) {
if (GetLastError() != ERROR_ALREADY_EXISTS) {
OutputDebugString(L"错误:无法创建 MiniMusicPlayer 文件夹\n");
return;
}
}
// 2. 定义配置文件和播放列表文件路径(CSV格式)
std::wstring configPath = folderPath + L"\\config.csv"; // 存当前索引、播放模式
std::wstring playlistPath = folderPath + L"\\playlist.csv"; // 存歌曲列表
// -------------------------- 保存配置信息(currentIndex, playMode)--------------------------
std::string configData;
configData += std::to_string(g_currentSongIndex);
configData += ",";
configData += std::to_string(static_cast<int>(g_currentMode));
// 一次性写入配置文件
HANDLE hConfigFile = CreateFileW(configPath.c_str(), GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hConfigFile != INVALID_HANDLE_VALUE) {
DWORD bytesWritten;
WriteFile(hConfigFile, configData.data(), (DWORD)configData.size(), &bytesWritten, NULL);
CloseHandle(hConfigFile);
}
else {
OutputDebugString(L"错误:无法创建 config.csv\n");
return;
}
// -------------------------- 保存歌曲列表(批量拼接+一次性写入)--------------------------
if (g_songList.empty()) {
// 若列表为空,创建空文件
HANDLE hEmptyFile = CreateFileW(playlistPath.c_str(), GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hEmptyFile != INVALID_HANDLE_VALUE) CloseHandle(hEmptyFile);
OutputDebugString(L"播放列表已保存(空列表)\n");
return;
}
// 拼接所有歌曲数据到内存缓冲区
std::string playlistData;
for (const auto& song : g_songList) {
// 字段1:filePath(转UTF-8+转义,用双引号包裹)
std::string filePath = WstringToUtf8Escaped(song.filePath);
playlistData += "\"" + filePath + "\"";
playlistData += ",";
// 字段2:fileName
std::string fileName = WstringToUtf8Escaped(song.fileName);
playlistData += "\"" + fileName + "\"";
playlistData += ",";
// 字段3:sizeBytes
playlistData += std::to_string(song.sizeBytes);
playlistData += ",";
// 字段4:fileSize
std::string fileSize = WstringToUtf8Escaped(song.fileSize);
playlistData += "\"" + fileSize + "\"";
playlistData += ",";
// 字段5:bitrateKbps
playlistData += std::to_string(song.bitrateKbps);
playlistData += ",";
// 字段6:bitrate
std::string bitrate = WstringToUtf8Escaped(song.bitrate);
playlistData += "\"" + bitrate + "\"";
playlistData += ",";
// 字段7:duration
char durationBuf[32];
sprintf_s(durationBuf, "%.6f", song.duration);
playlistData += durationBuf;
// 换行分隔不同歌曲
playlistData += "\n";
}
// 一次性写入播放列表文件
HANDLE hPlaylistFile = CreateFileW(playlistPath.c_str(), GENERIC_WRITE, 0, NULL,
CREATE_ALWAYS, FILE_ATTRIBUTE_NORMAL, NULL);
if (hPlaylistFile != INVALID_HANDLE_VALUE) {
DWORD bytesWritten;
WriteFile(hPlaylistFile, playlistData.data(), (DWORD)playlistData.size(), &bytesWritten, NULL);
CloseHandle(hPlaylistFile);
}
else {
OutputDebugString(L"错误:无法创建 playlist.csv\n");
return;
}
OutputDebugString(L"播放列表已保存(CSV格式)\n");
}
// 加载播放列表(CSV格式,快速解析)
void LoadPlaylist() {
WCHAR appDataPath[MAX_PATH] = { 0 };
if (SHGetFolderPathW(NULL, CSIDL_APPDATA, NULL, 0, appDataPath) != S_OK) {
OutputDebugString(L"错误:无法获取 APPDATA 路径\n");
return;
}
// 1. 定义文件路径
std::wstring folderPath = appDataPath;
folderPath += L"\\MiniMusicPlayer";
std::wstring configPath = folderPath + L"\\config.csv";
std::wstring playlistPath = folderPath + L"\\playlist.csv";
// -------------------------- 加载配置信息(currentIndex, playMode)--------------------------
HANDLE hConfigFile = CreateFileW(configPath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hConfigFile != INVALID_HANDLE_VALUE) {
char configBuf[1024] = { 0 };
DWORD bytesRead;
if (ReadFile(hConfigFile, configBuf, sizeof(configBuf) - 1, &bytesRead, NULL)) {
std::string configLine(configBuf);
std::vector<std::string> configFields = SplitCsvLine(configLine);
// 解析当前播放索引
if (configFields.size() >= 1) {
try {
g_currentSongIndex = std::stoi(configFields[0]);
}
catch (...) {
g_currentSongIndex = -1;
}
}
// 解析播放模式
if (configFields.size() >= 2) {
try {
int mode = std::stoi(configFields[1]);
if (mode >= 0 && mode <= 2) {
g_currentMode = static_cast<PlayMode>(mode);
}
}
catch (...) {
g_currentMode = PlayMode::AllLoop;
}
}
}
CloseHandle(hConfigFile);
}
else {
// 配置文件不存在,用默认值
g_currentSongIndex = -1;
g_currentMode = PlayMode::AllLoop;
OutputDebugString(L"警告:未找到 config.csv,使用默认配置\n");
}
// -------------------------- 加载歌曲列表(批量读取+逐行解析)--------------------------
HANDLE hPlaylistFile = CreateFileW(playlistPath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL,
OPEN_EXISTING, FILE_ATTRIBUTE_NORMAL, NULL);
if (hPlaylistFile == INVALID_HANDLE_VALUE) {
OutputDebugString(L"警告:未找到 playlist.csv(可能是第一次运行)\n");
return;
}
// 1. 一次性读取所有列表数据到内存
DWORD fileSize = GetFileSize(hPlaylistFile, NULL);
if (fileSize == INVALID_FILE_SIZE) {
CloseHandle(hPlaylistFile);
OutputDebugString(L"错误:无法获取 playlist.csv 大小\n");
return;
}
std::string playlistData(fileSize, 0);
DWORD bytesRead;
if (!ReadFile(hPlaylistFile, &playlistData[0], fileSize, &bytesRead, NULL)) {
CloseHandle(hPlaylistFile);
OutputDebugString(L"错误:无法读取 playlist.csv\n");
return;
}
CloseHandle(hPlaylistFile);
// 2. 逐行解析CSV数据
g_songList.clear();
std::stringstream ss(playlistData);
std::string line;
while (std::getline(ss, line)) {
if (line.empty()) continue;
// 分割当前行的字段
std::vector<std::string> fields = SplitCsvLine(line);
if (fields.size() < 7) continue;
SongInfo song;
try {
// 解析字段(UTF-8转宽字符)
song.filePath = Utf8ToWstring(fields[0]);
song.fileName = Utf8ToWstring(fields[1]);
song.sizeBytes = std::stoll(fields[2]);
song.fileSize = Utf8ToWstring(fields[3]);
song.bitrateKbps = std::stoi(fields[4]);
song.bitrate = Utf8ToWstring(fields[5]);
song.duration = std::stod(fields[6]);
// 检查文件是否存在
if (PathFileExistsW(song.filePath.c_str())) {
g_songList.push_back(song);
}
}
catch (...) {
// 解析失败,跳过当前歌曲
continue;
}
}
// 3. 修正当前播放索引(避免越界)
if (g_currentSongIndex < 0 || g_currentSongIndex >= (int)g_songList.size()) {
g_currentSongIndex = -1;
}
UpdateSongListUI();
OutputDebugString(L"播放列表已加载(CSV格式)\n");
}
void SetVolume(int volume)
{
if (hWaveOut == NULL) return;
DWORD vol = (DWORD)(volume * 65535.0 / 100.0);
vol = MAKELONG(vol, vol);
waveOutSetVolume(hWaveOut, vol);
}
void CALLBACK WaveOutProc(HWAVEOUT hwo, UINT uMsg, DWORD_PTR dwInstance,
DWORD_PTR dwParam1, DWORD_PTR dwParam2)
{
if (uMsg == WOM_DONE) {
WAVEHDR* pHdr = (WAVEHDR*)dwParam1;
if (!pHdr) return;
// 遍历查找匹配的缓冲区
int targetBufIdx = -1;
for (int i = 0; i < (int)g_buffers.size(); ++i) {
if (&g_buffers[i].hdr == pHdr) {
targetBufIdx = i;
break;
}
}
if (targetBufIdx == -1 || !g_buffers[targetBufIdx].prepared) {
OutputDebugString(L"WaveOutProc:未找到匹配的缓冲区或缓冲区未准备\n");
return;
}
// 处理目标缓冲区
PCMBuffer& targetBuf = g_buffers[targetBufIdx];
g_playedDuration += targetBuf.duration;
if (g_playedDuration > g_totalDuration) {
g_playedDuration = g_totalDuration;
}
// 回收缓冲区(线程安全)
std::lock_guard<std::mutex> lk(g_bufMutex);
bool isInFree = false;
std::queue<int> temp = g_freeBuffers;
while (!temp.empty()) {
if (temp.front() == targetBufIdx) { isInFree = true; break; }
temp.pop();
}
if (!isInFree) {
g_freeBuffers.push(targetBufIdx);
}
g_bufCv.notify_one();
// 触发进度更新
if (g_hwndMain) {
PostMessage(g_hwndMain, WM_USER_UPDATE_PROGRESS, 0, 0);
}
}
}
std::wstring GetFileSizeString(const std::wstring& filePath, int64_t& sizeBytes)
{
HANDLE hFile = CreateFileW(filePath.c_str(), GENERIC_READ, FILE_SHARE_READ, NULL, OPEN_EXISTING, 0, NULL);
if (hFile == INVALID_HANDLE_VALUE) {
sizeBytes = 0;
return L"未知";
}
LARGE_INTEGER fileSize;
if (!GetFileSizeEx(hFile, &fileSize)) {
CloseHandle(hFile);
sizeBytes = 0;
return L"未知";
}
CloseHandle(hFile);
sizeBytes = fileSize.QuadPart;
// 格式化文件大小
if (sizeBytes >= 1024 * 1024 * 1024) {
double sizeGB = (double)sizeBytes / (1024 * 1024 * 1024);
WCHAR buf[SMALL_BUFFER_SIZE];
swprintf_s(buf, SMALL_BUFFER_SIZE, L"%.2f GB", sizeGB);
return buf;
}
else if (sizeBytes >= 1024 * 1024) {
double sizeMB = (double)sizeBytes / (1024 * 1024);
WCHAR buf[SMALL_BUFFER_SIZE];
swprintf_s(buf, SMALL_BUFFER_SIZE, L"%.2f MB", sizeMB);
return buf;
}
else if (sizeBytes >= 1024) {
double sizeKB = (double)sizeBytes / 1024;
WCHAR buf[SMALL_BUFFER_SIZE];
swprintf_s(buf, SMALL_BUFFER_SIZE, L"%.2f KB", sizeKB);
return buf;
}
else {
WCHAR buf[SMALL_BUFFER_SIZE];
swprintf_s(buf, SMALL_BUFFER_SIZE, L"%lld B", sizeBytes);
return buf;
}
}
std::wstring GetBitrateString(const std::wstring& filePath, int& bitrateKbps)
{
std::string utf8_path = WstringToUtf8(filePath);
if (utf8_path.empty()) {
bitrateKbps = 0;
return L"未知";
}
AVFormatContext* fmt_ctx = NULL;
if (avformat_open_input(&fmt_ctx, utf8_path.c_str(), NULL, NULL) < 0) {
bitrateKbps = 0;
return L"未知";
}
if (avformat_find_stream_info(fmt_ctx, NULL) < 0) {
avformat_close_input(&fmt_ctx);
bitrateKbps = 0;
return L"未知";
}
int audio_stream_idx = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_AUDIO, -1, -1, NULL, 0);
int bitrate = 0;
if (audio_stream_idx >= 0) {
AVStream* audio_stream = fmt_ctx->streams[audio_stream_idx];
if (audio_stream->codecpar->bit_rate > 0) {
bitrate = audio_stream->codecpar->bit_rate;
}
else if (fmt_ctx->bit_rate > 0) {
bitrate = fmt_ctx->bit_rate;
}
}
avformat_close_input(&fmt_ctx);
bitrateKbps = bitrate / 1000; // 转换为kbps
if (bitrateKbps > 0) {
WCHAR buf[SMALL_BUFFER_SIZE];
swprintf_s(buf, SMALL_BUFFER_SIZE, L"%d kbps", bitrateKbps);
return buf;
}
else {
return L"未知";
}
}