播放视频的时候,如何同时获取实时音频流

这篇文章将会按照一般的需求开发流程,从需求、分析、开发,到总结,来给大家讲解一种“在 Android 设备上,播放视频的同时,获取实时音频流”的有效方案

一、需求 

在车载产品上,有这样一种需求,比如我把我的Android设备通过usb线连接上车机,这时我希望我在我Android手机上的操作,能同步到车机大屏上进行显示。

现在很多车机基本都是Android系统了,市场上也有类似CarPlay、CarLife这种专门做手机投屏的软件了。

不过呢,还有一部分的车子,他们的车机用的是Linux系统,这时如何实现Android设备和linux设备之间的屏幕信息同步呢?de292ef7d3d28eb19401c4239c6615e9.png

接下来的文章,我们只介绍其中的一种场景,就是我手机播放视频的时候,视频内容和视频的声音,都同步到linux系统的车机上。而且这篇文章,我们只介绍音频同步的内容。

二、分析 

两个设备之间的音频同步,那就是把一个设备中的音频数据同步到另一个设备上,一方做为发送端,另一方做为接收端,发送端不停的发生音频流,接收端接收到音频流,进行实时的播放,即可实现我们想要的效果。

说到设备之间的通信,相信很多同学会想到tcp、udp这些协议了。是的,考虑到tcp协议传输的有序性,而udp是无序的,我们传输的音频数据也是需要有序的,所有音频数据的传输,我们采用tcp协议。

接下来我们再了解下,在Android系统上,声音的播放流程是怎样的?这对我们如何去获取视频播放时候的音频流,很有帮助。

我们先看下关于视频的播放、录音,Android给我们提供了哪些API?

MediaRecorder 

接触过Android录像、录音的同学,应该对MediaRecorder 这个API不会感到模式。是的,在Android系统上,我们可以通过MediaRecorder API来很容易的实现录像、录音功能,下面是关于MediaRecorder 状态图,具体的使用,感兴趣的可以查看Android 官方文档(https://developer.android.google.cn/guide/topics/media/mediarecorder?hl=zh_cn)。

e50c56d01b24dca7019666e9a6776d97.png

MediaPlayer 

另外,用于播放视频的,Android为我们提供了MediaPlayer的接口(https://developer.android.google.cn/guide/topics/media/mediaplayer?hl=en)。

了解了上面的2个API,我们再来看下Android音频系统的框架图。89ae4e3fcad303a729995c45a7aec527.png

从上面的音频系统框架图(看画红线的部分),我们可以知道,应用上调用MediaPlayer、MediaRecorder来播放、录音,在framewrok层会调用到AudioTrack.cpp这个文件。

那么回到文章的重点,我们需要在播放视频的时候,把视频的音频流实时的截取出来。那截取音频流的这部分工作,就可以放在AudioTrack.cpp中进行处理。

我们来看下AudioTrack.cpp里面比较重要的方法

ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking) { if (mTransfer != TRANSFER_SYNC) { return INVALID_OPERATION; }
if (isDirect()) {
    AutoMutex lock(mLock);
    int32_t flags = android_atomic_and(
                        ~(CBLK_UNDERRUN | CBLK_LOOP_CYCLE | CBLK_LOOP_FINAL | CBLK_BUFFER_END),
                        &mCblk->mFlags);
    if (flags & CBLK_INVALID) {
        return DEAD_OBJECT;
    }
}


if (ssize_t(userSize) < 0 || (buffer == NULL && userSize != 0)) {
    // Sanity-check: user is most-likely passing an error code, and it would
    // make the return value ambiguous (actualSize vs error).
    ALOGE("AudioTrack::write(buffer=%p, size=%zu (%zd)", buffer, userSize, userSize);
    return BAD_VALUE;
}


size_t written = 0;
Buffer audioBuffer;


while (userSize >= mFrameSize) {
    audioBuffer.frameCount = userSize / mFrameSize;


    status_t err = obtainBuffer(&audioBuffer,
            blocking ? &ClientProxy::kForever : &ClientProxy::kNonBlocking);
    if (err < 0) {
        if (written > 0) {
            break;
        }
        if (err == TIMED_OUT || err == -EINTR) {
            err = WOULD_BLOCK;
        }
        return ssize_t(err);
    }


    size_t toWrite = audioBuffer.size;
    memcpy(audioBuffer.i8, buffer, toWrite);     


    mBuffer = malloc(toWrite);
    memcpy(mBuffer,buffer,toWrite);


    if(mCurrentPlayMusicStream && mSocketHasInit){
       onSocketSendData(toWrite);
     }




    buffer = ((const char *) buffer) + toWrite;
    userSize -= toWrite;
    written += toWrite;


    releaseBuffer(&audioBuffer);
}


if (written > 0) {
    mFramesWritten += written / mFrameSize;
}
return written;

三、实现 

前面分析了一通,我们的方案也比较明朗了,就是在framework层的AudioTrack.cpp文件中,通过socket,把音频流实时的发送出来。

另一个就是接收端,不停的接收发送出来的socket数据,这个socket数据就是实时的pcm流,接收方,在实时播放pcm流,就能实现音频的实时同步了。

关于视频流,是如何实现同步的,大家也可以猜猜?

1)AudioTrack.cpp中的代码实现

#define DEST_PORT 5046
#define DEST_IP_ADDRESS "192.168.7.6"


int mSocket;
bool mSocketHasInit;
bool mCurrentPlayMusicStream;
struct sockaddr_in mRemoteAddr;


   ssize_t AudioTrack::write(const void* buffer, size_t userSize, bool blocking)
{
       ......
        size_t toWrite = audioBuffer.size;
        memcpy(audioBuffer.i8, buffer, toWrite);     


        mBuffer = malloc(toWrite);
        memcpy(mBuffer,buffer,toWrite);
        //我们添加的代码:把音频流实时的发送出去
       if(mCurrentPlayMusicStream && mSocketHasInit){
           onSocketSendData(toWrite);
         }
        ......
}


int AudioTrack::onSocketSendData(uint32_t len){
       assert(NULL != mBuffer);
       assert(-1 != len);


      if(!mSocketHasInit){
          initTcpSocket();
       }


      unsigned int ret = send(mSocket, mBuffer,len, 0);   
      free(mBuffer);
      return 0;
}

2) 接收端的代码处理

(我这里是用的Android设备调试,如果是linux系统,思路是同样的)

接收端的处理逻辑流程图如下:

   1、设置socket监听;

   2、循环监听socket端口数据;

   3、接收到pcm流;

   4、播放pcm流;673a1ff655a1be3878d43ace1fdd0c59.png

---------- PlayActivity.java ----------------------------


    private ServerSocket mTcpServerSocket = null;
    private List<Socket> mSocketList = new ArrayList<>();
    private MyTcpListener mTcpListener = null;


    private boolean isAccept = true;
    /**
     * 设置socket监听
     */
    public void startTcpService() {
        Log.v(TAG,"startTcpService();");
        if(mTcpListener == null){
            mTcpListener = new MyTcpListener();
        }


        new Thread() {
            @Override
            public void run() {
                super.run();
                try {
                    mTcpServerSocket = new ServerSocket();
                    mTcpServerSocket.setReuseAddress(true);
                    InetSocketAddress socketAddress = new InetSocketAddress(AndroidBoxProtocol.TCP_AUDIO_STREAM_PORT);
                    mTcpServerSocket.bind(socketAddress);


                    while (isAccept) {
                        Socket socket = mTcpServerSocket.accept();
                        mSocketList.add(socket);


                        //开启新线程接收socket 数据
                        new Thread(new TcpServerThread(socket,mTcpListener)).start();
                    }
                } catch (Exception e) {
                    Log.e("TcpServer", "" + e.toString());
                }
            }
        }.start();
    }
 /**
   * 停止socket监听
  */
 private void stopTcpService(){


        isAccept = false;
        if(mTcpServerSocket != null){
            new Thread() {
                @Override
                public void run() {
                    super.run();
                    try {
                        for(Socket socket:mSocketList) {
                            socket.close();
                        }
                        mTcpServerSocket.close();
                    } catch (IOException e) {
                        e.printStackTrace();
                    }
                }
            }.start();
        }
    }


    /**
     * 播放pcm 实时流
     * @param buffer
     */
    private void playPcmStream(byte[] buffer) {
        if (mAudioTrack != null && buffer != null) {
            mAudioTrack.play();
            mAudioTrack.write(buffer, 0, buffer.length);
        }
    }


  private Handler mUiHandler = new Handler() {
        @Override
        public void handleMessage(Message msg) {
            super.handleMessage(msg);
            switch (msg.what) {
                case HANDLER_MSG_PLAY_PCM:
                    playPcmStream((byte[]) msg.obj);
                    break;
                default:
                    break;
            }
        }
    };


 private class MyTcpListener  implements ITcpSocketListener{
        @Override
        public void onRec(Socket socket, byte[] buffer) {
          sendHandlerMsg(HANDLER_MSG_PLAY_PCM,0,buffer);
        }
    }

四、总结

刚开始接到这个开发需求,也是思考了良久才想到这个方案。也再次验证了,熟悉了解framework层,可以给我们提供很多实现问题的思路。中间调试的时候,也是遇到了不少的问题。不过欣喜的是结果还不错,最后都给跑通了。

该思路,希望对大家有帮助。

14a23165d7274157f0c8f4c14cfaac43.png

《Android Camera开发入门》、《Camx初认识》已经上架,可以点击了解 -> 小驰成长圈 |期待见证彼此的成长 08172fad22487285335391b666a9ed56.png

c3470d877930e2eaeaa1c28f0d5a8c09.png

觉得不错,点个赞呗 8d5d76357a3c5036aed53804f5e6a5ed.png

  • 1
    点赞
  • 5
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 0
    评论
### 回答1: 关于QT实时播放音频的技术,可以使用QT的Multimedia模块来实现。你可以使用QMediaPlayer类来播放音频,可以通过设置QMediaPlayer的Media源为音频的URL来实现。此外,你还可以使用QT的QAudioInput和QAudioOutput类来录制和播放音频。这些类提供了一些方法来处理音频数据,如设置音频格式、读取和写入音频数据等。因此,使用这些类可以很方便地实现QT实时播放音频的功能。 ### 回答2: QT实时播放音频的技术是指利用QT框架的相关功能和库来实现实时播放音频的功能。在QT中,可以使用QAudioOutput类来进行音频的播放。 首先,需先创建一个QIODevice对象,并将其传递给QAudioOutput实例,以便进行数据的写入和播放。可以使用QT的QBuffer类来创建一个可读写的数据缓冲区,然后将数据写入缓冲区。 接下来,需设置QAudioFormat对象,用于描述音频的参数,包括采样率、声道数、采样位数等。通过设置QIODevice的打开模式和格式,可以实现音频的读取和写入。 然后,创建QAudioOutput实例,并将之前创建的QIODevice对象传递给它。可以设置音频输出设备的参数,如音量、音频编码等。 最后,通过调用QAudioOutput的start()函数,开始音频的播放,同时将数据数据写入QIODevice对象,并且可以通过QAudioOutput的stateChanged信号来获取音频的播放状态。 在接收到音频数据时,可以通过读取音频数据并写入到QIODevice对象中实现实时播放。当音频结束时,可以调用QAudioOutput的stop()函数来停止播放,同时进行资源释放。 总之,通过使用QT的QAudioOutput类和相关的功能,我们可以很方便地实现QT实时播放音频的技术。 ### 回答3: QT是一种跨平台的应用程序开发框架,可以用于开发各种类型的应用程序,包括音频播放应用。QT提供了一些实时播放音频的技术,下面我将详细介绍一些常用的技术。 首先,QT提供了一个音频处理类QAudioOutput,它允许从音频中读取数据并将其通过音频设备实时播放。开发者可以通过QAudioOutput的相关接口设置音频的格式、采样率和声道等参数,并通过write()函数将音频数据写入缓冲区,在音频设备准备好播放时,QAudioOutput会自动从缓冲区中读取数据并实时播放。 其次,QT还提供了一个音频采集类QAudioInput,它可以实时音频设备中采集音频数据。开发者可以通过QAudioInput的相关接口设置音频数据的格式、采样率和声道等参数,并通过start()函数开始采集音频数据。采集到的音频数据可以通过read()函数读取,并进行后续处理或实时播放。 此外,QT还支持使用QMediaPlayer类实时播放音频。QMediaPlayer是一个高级的多媒体播放器类,可以播放各种类型的音频和视频文件。开发者可以通过设置音频的URL或本地文件路径来播放音频。QMediaPlayer提供了一些常用的接口,如play()、pause()和stop()等,可以对音频进行控制。 总之,QT提供了丰富的实时播放音频的技术,开发者可以根据自己的需求选择合适的技术进行音频的处理和播放。无论是使用QAudioOutput、QAudioInput还是QMediaPlayer,QT都提供了简洁易用的接口,方便开发者进行音频应用的开发。
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

小驰行动派

谢谢老板,今晚吃鸡~

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值