手机秒变音响神器!

一、背景与介绍

有时候,电脑可能没有喇叭,或者你忘了带耳机,结果就听不到电脑上的声音了。但有时候你可能希望用手机来听电脑上的声音。可问题是电脑的声音不直接传到手机上。所以我们需要一个方法,能把电脑上的声音传到手机上去听。

解决方案概述

为了解决将电脑声音通过手机播放的问题,我们采用了以下解决方案:

在电脑端,我们使用了Qt框架编写了一个程序,主要负责音频的采集。通过该程序,我们能够实时捕获电脑的音频输出。 在Android端,我们则利用了Android系统的app_process程序,无需额外安装app。该程序能够接收来自电脑端的音频数据,并将其实时播放出来。

该项目已在GitHub平台上开源,您可以通过以下链接访问项目:

github.com/ysbing/Audi...

AudioShare是一款音频流传输应用,它能够实现实时从电脑向手机传输音频流,让用户在电脑缺乏外接扬声器或未带耳机的情况下,依然能通过手机扬声器或耳机播放电脑中的音频内容。

二、Windows客户端实现

声音采集

  • 获取音频设备信息

使用了 Windows 的 Core Audio API,这是一组用于音频设备管理的 API。 调用了 CoCreateInstance 函数来创建一个设备枚举器对象,用于获取系统中的音频设备。 通过调用 EnumAudioEndpoints 函数来枚举系统中的音频输出设备,并将其保存在一个设备集合对象中。

cpp 复制代码
HRESULT hr;
IMMDeviceEnumerator *pEnumerator = NULL;
IMMDeviceCollection *pDevices = NULL;

hr = CoCreateInstance(CLSID_MMDeviceEnumerator, NULL, CLSCTX_ALL, IID_IMMDeviceEnumerator, (void**)&pEnumerator);
if (FAILED(hr)) {
    // 错误处理
}

hr = pEnumerator->EnumAudioEndpoints(eRender, DEVICE_STATE_ACTIVE, &pDevices);
if (FAILED(hr)) {
    // 错误处理
}
  • 选择目标音频设备: 通过设备的 ID 来获取特定的音频输出设备。在设备集合中遍历每一个设备,逐一比较设备的 ID 是否与目标设备的 ID 匹配。当找到匹配的设备后,即可返回该设备对象。
cpp 复制代码
UINT count;
IMMDevice *pDevice = NULL;
WCHAR *pName;

hr = pDevices->GetCount(&count);
if (FAILED(hr)) {
    // 错误处理
}

for (UINT i = 0; i < count; i++){
    hr = pDevices->Item(i, &pDevice);
    if (FAILED(hr)) {
        // 错误处理
    }
    
    hr = pDevice->GetId(&pName);
    if (FAILED(hr)) {
        // 错误处理
    }

    if (lstrcmp(pName, pId) == 0){
        // 找到目标设备
        CoTaskMemFree(pName);
        break;
    }

    CoTaskMemFree(pName);
    SAFE_RELEASE(pDevice);
}
  • 初始化音频采集: 调用了 Activate 函数,使用设备对象激活了音频客户端接口。使用了 IAudioClient 接口来初始化音频采集。在这里,指定了音频采集的共享模式和其他参数,如音频格式等。
cpp 复制代码
// 创建音频客户端对象并激活
HRESULT hr = pDevice->Activate(IID_IAudioClient, CLSCTX_ALL, NULL, (void**)ppAudioClient);
if (FAILED(hr)) {
    // 错误处理
    return hr;
}
// 设置音频格式等参数
// ...
return S_OK;
  • 实时音频采集: 使用了 IAudioCaptureClient 接口来获取音频数据并进行实时采集。循环调用了 GetNextPacketSize 函数,获取音频数据包的大小,然后使用 GetBuffer 函数获取音频数据。获取到音频数据后,通过自定义的 workSocket->write 函数将数据传输到 Android 端。
cpp 复制代码
// 获取音频采集客户端
IAudioCaptureClient *pCaptureClient = NULL;
HRESULT hr = pAudioClient->GetService(IID_IAudioCaptureClient, (void**)&pCaptureClient);
if (FAILED(hr)) {
    // 错误处理
    return hr;
}
// 开始音频采集
hr = pAudioClient->Start();
if (FAILED(hr)) {
    // 错误处理
    return hr;
}
// 实时采集音频数据
// ...

数据传输

为了实现数据传输,我们采用了以下步骤:

  • 等待客户端连接:WorkSocket 类的设计中,workSocket 在实例化时即完成了 QTcpServer 的初始化工作,从而开始监听来自 Android 设备的连接请求。在构造函数中,我们创建了一个 QTcpServer 实例,并绑定到指定端口。
cpp 复制代码
WorkSocket::WorkSocket(int port)
{
    QTcpServer* server = new QTcpServer(this);
    if (server->listen(QHostAddress::Any, port)) {
        connect(server, &QTcpServer::newConnection, this, &WorkSocket::newClientHandler);
    } else {
        // 处理监听失败的情况
    }
}

// 新客户端连接处理器
void WorkSocket::newClientHandler()
{
    QTcpSocket* clientSocket = server->nextPendingConnection();
    // 连接成功后,后续的 write 函数将通过这个客户端套接字发送音频数据到Android设备
}
  • 数据传输到Android设备: 一旦建立了与Android设备的连接,就可以通过客户端套接字将数据传输到Android设备上。
cpp 复制代码
// 发送音频数据到已连接的Android设备
void WorkSocket::write(const char *data, int len)
{
    if (clientSocket && clientSocket->isValid() && clientSocket->isWritable()) {
        clientSocket->write(data, len);
        clientSocket->flush();
    }
}

通过以上步骤,我们成功实现了在Windows客户端接收数据,并将数据传输到Android设备上的功能。

三、Android端实现

数据接收

在Android端,我们通过以下步骤实现了数据接收:

  • 建立与Windows客户端的连接: Android端使用了LocalSocket来连接Windows客户端的Socket,建立起通信连接。
java 复制代码
private static LocalSocket connect(String abstractName, String connectCode) throws IOException {
    // 创建本地Socket并连接Windows客户端的Socket
    LocalSocket localSocket = new LocalSocket();
    localSocket.connect(new LocalSocketAddress(abstractName));
    OutputStream outputStream = localSocket.getOutputStream();
    outputStream.write(connectCode.getBytes());
    outputStream.flush();
    return localSocket;
}
  • 接收数据流: 一旦成功连接到Windows客户端的Socket,Android端就可以通过InputStream来接收来自Windows客户端的数据流。
java 复制代码
Options options = Options.parse(args);
LocalSocket socket = connect(options.socketName, options.connectCode);
play(socket.getInputStream());

通过以上步骤,我们成功在Android端建立了与Windows客户端的连接,并准备好接收来自Windows客户端的数据流。

声音播放

为了在Android端播放来自Windows客户端的声音,我们采用了以下步骤:

  • 准备音频播放器: 首先,我们根据设备要求获取音频播放器的最小缓冲区大小,然后创建一个AudioTrack对象,用于播放音频数据流。
java 复制代码
int bufferSize = AudioTrack.getMinBufferSize(48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT);
AudioTrack audioTrack;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
    audioTrack = new AudioTrack.Builder()
            .setAudioAttributes(new AudioAttributes.Builder()
                    .setUsage(AudioAttributes.USAGE_MEDIA)
                    .setContentType(AudioAttributes.CONTENT_TYPE_MUSIC)
                    .build())
            .setAudioFormat(new AudioFormat.Builder()
                    .setEncoding(AudioFormat.ENCODING_PCM_16BIT)
                    .setSampleRate(48000)
                    .setChannelMask(AudioFormat.CHANNEL_OUT_STEREO)
                    .build())
            .setBufferSizeInBytes(bufferSize)
            .setTransferMode(AudioTrack.MODE_STREAM)
            .build();

} else {
    audioTrack = new AudioTrack(AudioManager.STREAM_MUSIC, 48000, AudioFormat.CHANNEL_OUT_STEREO, AudioFormat.ENCODING_PCM_16BIT, bufferSize, AudioTrack.MODE_STREAM);
}
  • 播放音频: 然后,在 Android 应用程序中,当成功连接到 Windows 客户端的 Socket 后,我们从 Socket 输入流 (InputStream) 中读取音频数据。这些数据随后被写入到 Android 系统的 AudioTrack 对象中,从而实现实时音频播放:
java 复制代码
// 成功连接到Windows客户端后
InputStream inputStream = socket.getInputStream();

// 音频播放循环
byte[] audioBuffer = new byte[bufferSize];
while (!Thread.currentThread().isInterrupted()) {
    int bytesRead = inputStream.read(audioBuffer, 0, bufferSize);
    if (bytesRead > 0) { // 修正条件判断,只处理有效读取数据的情况
        audioTrack.write(audioBuffer, 0, bytesRead);
    }
}

// 不再播放时记得停止和释放资源
audioTrack.stop();
audioTrack.release();

这段代码不断从输入流中读取音频数据,并将其写入到AudioTrack的缓冲区中,实现了实时的音频播放。

四、系统架构与流程

  • 端口选择与映射: 在Windows客户端,首先进行端口选择,确保选用一个未被占用的端口用于通信。接下来,通过adb reverse命令,将所选端口与Android设备上的本地套接字进行映射。这一步骤的目的是确保Windows客户端与Android端能够建立有效的通信连接。

  • Windows客户端启动: 在Windows客户端上启动一个监听服务,以等待Android设备的连接请求。监听服务将监听所选端口,并在接收到Android设备的连接请求后建立连接,确保双方能够进行有效的通信。

  • Android应用部署: 在部署阶段,通过adb命令将预先编译好的Android应用程序的jar包推送到目标Android设备上。这一步骤是为了为后续通信建立做好准备,确保Android端能够正常运行应用程序并进行通信。

  • Android应用启动: 在Android设备上执行adb shell app_process命令,将所选端口和本地套接字名称作为参数传递给Android应用程序,并启动应用程序。这一步骤的目的是确保Android应用程序能够正确获取通信所需的参数,并进行通信的初始化。

  • Android端连接: Android应用程序在启动后,尝试连接到Windows客户端的监听服务。通过建立连接,Android端能够与Windows客户端进行双向通信,为后续的数据传输做好准备。

  • 数据传输与交互: 一旦Android端成功连接到Windows客户端,便开始进行声音数据的传输。Windows客户端采集到的声音数据经过编码处理后发送到Android端,Android端接收到数据后通过音频播放器进行解码并播放出声音。这样,实现了从Windows电脑向Android设备传输声音的功能。

通过以上流程,我们建立了一个可靠的系统架构,实现了Windows客户端与Android端之间的实时数据交互,为用户提供了便捷的声音传输方案。

五、使用示例

六、结论与展望

在本项目中,我们成功实现了将Windows电脑上的声音传输到Android设备并实时播放的功能。通过精心设计的系统架构和流程,我们确保了通信的稳定性和效率,为用户提供了便捷的解决方案。

通过本项目的实践,我们得出以下结论:

  • 技术可行性验证: 通过本项目,我们验证了将声音从Windows电脑传输到Android设备并实时播放的技术可行性。通过合理的系统设计和技术实现,我们成功地克服了各种挑战,实现了稳定可靠的声音传输。

  • 用户体验优化: 本项目旨在解决用户在没有安装扬声器或无法使用耳机的情况下无法听到电脑声音的问题。通过将声音传输到用户常用的Android设备并实时播放,大大提高了用户的使用体验,让用户能够随时随地享受电脑声音。 在未来的工作中,我们可以进一步完善和优化本项目,提升其性能和功能,包括但不限于:

  1. 跨平台支持: 考虑到用户可能使用不同类型的电脑系统,我们可以进一步扩展项目,支持更多平台,如Mac或Linux设备等。
  2. 功能拓展: 在基本功能的基础上,可以考虑添加一些附加功能,如音量调节、声音效果等,以提升用户体验。
  3. 性能优化: 对系统进行性能优化,减少通信延迟和资源占用,提高系统的稳定性和响应速度。

通过持续的努力和改进,我们相信本项目将为用户提供更加优质的声音传输服务,并在未来发展中拥有更广阔的应用前景。

相关推荐
想要打 Acm 的小周同学呀1 小时前
ThreadLocal学习
android·java·学习
天下是个小趴菜1 小时前
蚁剑编码器编写——中篇
android
命运之手1 小时前
【Android】自定义换肤框架05之Skinner框架集成
android·skinner·换肤框架·不重启换肤·无侵入换肤
DS小龙哥1 小时前
QT+OpenCV在Android上实现人脸实时检测与目标检测
android·人工智能·qt·opencv·目标检测
SwBack2 小时前
【pearcmd】通过pearcmd.php 进行GetShell
android·开发语言·php
miao_zz2 小时前
react native中依赖@react-native-clipboard/clipboard库实现文本复制以及在app中获取复制的文本内容
android·react native·react.js
小羊子说2 小时前
Android 开发中 C++ 和Java 日志调试
android·java·c++
Android 开发者2 小时前
平台稳定性里程碑 | Android 15 Beta 3 已发布
android
命运之手2 小时前
【Android】自定义换肤框架02之自定义AssetManager和Resource
android·skin·skinner·换肤框架·不重启换肤
cjzcjl4 小时前
Android OpenGL ES 离屏幕渲染1——EGL环境的创建,以及基础概念的理解
android·创建·egl·eglcontext·eglsurface