一旦在开发过程中遇到编码问题,保证你一天的心情将会糟糕透顶, 一个文件编码就能影响你一天的工作。为了总结经验,特地将整个过程重现一遍。
整个工程是基于Visual Studio 2022开发的Qt工程。
根据向导创建完成后,没有去查看文件的编码,就直接开干。刚开始遇见了一个小问题,运行过程中发现窗口标题居然是乱码。我也没去深究,想到这个很简单嘛,使用QString::fromLocal8Bit()函数就能解决问题。
将代码
this->setWindowTitle("录音");
修改为
this->setWindowTitle(QString::fromLocal8Bit("录音"));
之后,窗口标题乱码问题确实解决了。
然后,继续编写核心代码,使用FFmpeg打开音频输入设备。
引入头文件和库文件
extern "C"
{
#include <libavcodec/avcodec.h>
#include <libavdevice/avdevice.h>
#include <libavformat/avformat.h>
#pragma comment(lib,"avcodec.lib")
#pragma comment(lib,"avdevice.lib")
#pragma comment(lib,"avformat.lib")
}
编写如下逻辑,感觉良好。
avdevice_register_all();
AVFormatContext* context = nullptr;
const AVInputFormat* format = av_find_input_format("dshow");
char device[] = "audio=麦克风阵列 (Realtek High Definition Audio)";
int ret = avformat_open_input(&context, device, format, nullptr);
if (ret < 0) {
qDebug() << "无法打开音频输入设备";
return;
}
QFile file("./my.pcm");
file.open(QIODevice::WriteOnly);
if (!file.isOpen())
{
qDebug() << "文件打开失败";
return;
}
AVPacket packet;
int count = 10;
while (--count > 0 && av_read_frame(context, &packet) == 0)
{
file.write((const char*)packet.data, packet.size);
}
file.close();
avformat_close_input(&context);
可是,在运行过程中发现不是那么回事。首先在
qDebug() << "文件打开失败";
这行代码,就发现一个非常奇怪的问题,居然编译不通过。
于是我不管三七二十一,直接将它注释掉,编译通过。
当时的我一脸懵逼,还以为是Qt与Visual Studio集成出现的兼容性问题,不能使用qDebug()进行调试程序。把
qDebug() << "文件打开失败";
这行代码注释掉之后,继续调试程序。此时,发现控制台出现很多乱码,而且从输出信息上面看到:
程序无法找到音频设备,但是从另一个侧面,我是能够获取到该设备信息的。
通过命令行,输入如下命令可以获取音频输入设备信息。
ffmpeg -list_devices true -f dshow -i dummy
这行代码
int ret = avformat_open_input(&context, device, format, nullptr);
的返回值总是-5。好像真的没有什么头绪了哟。通过不断的思考和查找解决方案,说是“音频输入设备名称中含有中文,需要进行编码转换”,将ANSI编码转换为UTF-8编码就可以了。
我尝试着这种解决方案,加入了如下的转换函数。
std::string AnsiToUTF8(const char* _ansi, int _ansi_len)
{
std::string str_utf8("");
wchar_t* pUnicode = NULL;
BYTE* pUtfData = NULL;
do
{
int unicodeNeed = MultiByteToWideChar(CP_ACP, 0, _ansi, _ansi_len, NULL, 0);
pUnicode = new wchar_t[unicodeNeed + 1];
memset(pUnicode, 0, (unicodeNeed + 1) * sizeof(wchar_t));
int unicodeDone = MultiByteToWideChar(CP_ACP, 0, _ansi, _ansi_len, (LPWSTR)pUnicode, unicodeNeed);
if (unicodeDone != unicodeNeed)
{
break;
}
int utfNeed = WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)pUnicode, unicodeDone, (char*)pUtfData, 0, NULL, NULL);
pUtfData = new BYTE[utfNeed + 1];
memset(pUtfData, 0, utfNeed + 1);
int utfDone = WideCharToMultiByte(CP_UTF8, 0, (LPWSTR)pUnicode, unicodeDone, (char*)pUtfData, utfNeed, NULL, NULL);
if (utfNeed != utfDone)
{
break;
}
str_utf8.assign((char*)pUtfData);
} while (false);
if (pUnicode)
{
delete[] pUnicode;
}
if (pUtfData)
{
delete[] pUtfData;
}
return str_utf8;
}
该函数需要引入头文件
#include <Windows.h>
,然后在使用函数
avformat_open_input(&context, device, format, nullptr);
打开音频输入设备之前,调用AnsiToUTF8函数,进行编码转换。经过尝试,果然可以。
std::string result = AnsiToUTF8(device, sizeof(device));
int ret = avformat_open_input(&context, result.c_str(), format, nullptr);
突然,好像什么都明白了一样。转过去查看了一下该程序文件的编码,吓了一跳,原来就是这该死的编码在作怪。
终于明白了整个问题的原委。于是我索性将它更改为UTF-8编码。
此时,运行程序再次出现乱码。
不过这时的我已不再像之前那样恐慌了,我将这行代码
this->setWindowTitle(QString::fromLocal8Bit("录音"));
修改为
this->setWindowTitle("录音");
同时,将之前引入的转换ANSI编码为UTF-8编码的那部分代码去掉。因为已不再需要它的帮助了。完成后再次运行测试,完美解决问题。
最后,我重新回过头来发现,原来之前报的那个错误其实就已经告诉我了,程序文件的编码与函数调用需要的编码不一致,导致无法找到音频输入设备的错误。现在也明白了为什么
qDebug() << "文件打开失败";
这行代码会导致编译失败的问题。经过我仔细的分析与调试,原来"文件打开失败"这句话中的“打”和“败”这两个中文字符在GB2312编码下与ANSI编码发生冲突。
我总结了一下,作为程序员的我们还是得多思考为什么,多总结经验教训。不然有一天确实是会被程序世界与现实世界一起联合给“打败”。