Kivy低延迟音频也在Android上

如何解决Kivy低延迟音频也在Android上

我想写一个以Kivy为前端的音乐DAW /合成器/鼓机。

有没有办法用Kivy做低延迟音频?

(理想情况下,它还将在Android上编译并运行)

解决方法

如果在任何人都需要再次在Android或Windows / Linux / Mac上使用Kivy进行低延迟/实时音频播放或输入/记录的情况下,我将在这里包括整个解决方案:

在选择我所选择的路径之前,请注意:我现在正经历明显的按钮单击延迟,尤其是在Windows上。这可能是我的项目的热门,也可能是您的。在开始使用与Cython集成的C ++库进行Android编译之前,请测试您的输入延迟!

如果想了解为什么setup.pypython-for-android食谱中存在某些有趣的行,请搜索过去几天的StackOverflow历史记录。

我最终直接与miniaudio一起使用Cython

# engine.py
import cython
from midi import Message

cimport miniaudio
# this is my synth in another Cython file,you'll need to supply your own
from synthunit cimport RingBuffer,SynthUnit,int16_t,uint8_t

cdef class Miniaudio:
    cdef miniaudio.ma_device_config config
    cdef miniaudio.ma_device device

    def __init__(self,Synth synth):
        cdef void* p_data
        p_data = <void*>synth.get_synth_unit_address()
        self.config = miniaudio.ma_device_config_init(miniaudio.ma_device_type.playback);
        self.config.playback.format   = miniaudio.ma_format.s16
        self.config.playback.channels = 1
        self.config.sampleRate        = 0
        self.config.dataCallback      = cython.address(callback)
        self.config.pUserData         = p_data

        if miniaudio.ma_device_init(NULL,cython.address(self.config),cython.address(self.device)) != miniaudio.ma_result.MA_SUCCESS:
            raise RuntimeError("Error initializing miniaudio")

        SynthUnit.Init(self.device.sampleRate)

    def __enter__(self):
        miniaudio.ma_device_start(cython.address(self.device))

    def __exit__(self,type,value,tb):
        miniaudio.ma_device_uninit(cython.address(self.device))

cdef void callback(miniaudio.ma_device* p_device,void* p_output,const void* p_input,miniaudio.ma_uint32 frame_count) nogil:
    # this function must be realtime (never ever block),hence the `nogil`
    cdef SynthUnit* p_synth_unit
    p_synth_unit = <SynthUnit*>p_device[0].pUserData
    output = <int16_t*>p_output
    p_synth_unit[0].GetSamples(frame_count,output)
    # debug("row",0)
    # debug("frame_count",frame_count)
    # debug("freq mHz",int(1000 * p_synth_unit[0].freq))

cdef class Synth:
    # wraps synth in an object that can be used from Python code,but can provice raw pointer
    cdef RingBuffer ring_buffer
    cdef SynthUnit* p_synth_unit

    def __cinit__(self):
        self.ring_buffer = RingBuffer()
        self.p_synth_unit = new SynthUnit(cython.address(self.ring_buffer))

    def __dealloc__(self):
        del self.p_synth_unit

    cdef SynthUnit* get_synth_unit_address(self):
        return self.p_synth_unit

    cpdef send_midi(self,midi):
        raw = b''.join(Message(midi,channel=1).bytes_content)
        self.ring_buffer.Write(raw,len(raw))

# can't do debug prints from a realtime function,but can write to a buffer:
cdef int d_index = 0
ctypedef long long addr
cdef addr[1024] d_data
cdef (char*)[1024] d_label

cdef void debug(char* label,addr x) nogil:
    global d_index
    if d_index < sizeof(d_data) * sizeof(d_data[0]):
        d_label[d_index] = label
        d_data[d_index] = x
        d_index += 1

def get_debug_data():
    result = []
    row = None
    for i in range(d_index):
        if d_label[i] == b"row":
            result.append(row)
            row = []
        else:
            row.append((d_label[i],d_data[i]))
    result.append(row)
    return result
# miniaudio.pxd
cdef extern from "miniaudio_define.h":
    pass # needed to do a #define that miniaudio.h expects,just put it in another C header

cdef extern from "miniaudio.h":
    ctypedef unsigned int ma_uint32
    cdef enum ma_result:
        MA_SUCCESS = 0
    cdef enum ma_device_type:
        playback "ma_device_type_playback" = 1
        capture "ma_device_type_capture"  = 2
        duplex "ma_device_type_duplex"   = playback | capture
        loopback "ma_device_type_loopback" = 4
    cdef enum ma_format:
        unknown "ma_format_unknown" = 0
        u8 "ma_format_u8"      = 1
        s16 "ma_format_s16"     = 2
        s24 "ma_format_s24"     = 3
        s32 "ma_format_s32"     = 4
        f32 "ma_format_f32"     = 5
    ctypedef struct ma_device_id:
        pass
    ctypedef struct ma_device_config_playback:
        const ma_device_id* pDeviceID
        ma_format format
        ma_uint32 channels
    ctypedef void (* ma_device_callback_proc)(ma_device* pDevice,void* pOutput,const void* pInput,ma_uint32 frameCount)
    ctypedef struct ma_device_config:
        ma_uint32 sampleRate
        ma_uint32 periodSizeInMilliseconds
        ma_device_config_playback playback
        ma_device_callback_proc dataCallback
        void* pUserData
    ctypedef struct ma_device:
        ma_uint32 sampleRate
        void* pUserData
        ma_context* pContext
    ctypedef struct ma_context:
        pass
    ma_device_config ma_device_config_init(ma_device_type deviceType)
    ma_result ma_device_init(ma_context* pContext,const ma_device_config* pConfig,ma_device* pDevice)
    ma_result ma_device_start(ma_device* pDevice)
    void ma_device_uninit(ma_device* pDevice)
// minidaudio_define.h
#define MA_NO_DECODING
#define MA_NO_ENCODING
#define MINIAUDIO_IMPLEMENTATION

miniaudio.h中的miniaudio必须位于同一目录中。

# setup.py
from setuptools import setup,Extension
from Cython.Build import cythonize

setup(
    name = 'engine',version = '0.1',ext_modules = cythonize([Extension("engine",["engine.pyx"] + ['synth/' + p for p in [
            'synth_unit.cc','util.cc'
        ]],include_path = ['synth/'],language = 'c++',)])
)

由于pymidi在Android上由于import serial不起作用而崩溃,并且由于我还不知道编写python-for-android食谱和添加补丁,因此我只添加了{{1 }}对我的根目录没有任何作用:

serial.py

最后是""" Override pySerial because it doesn't work on Android. TODO: Use https://source.android.com/devices/audio/midi to implement MIDI support for Android """ Serial = lambda *args,**kwargs: None main.py必须调用它):

python-for-android

要在Windows上进行构建,只需将# main.py class MyApp(App): # a Kivy app ... if __name__ == '__main__': synth = engine.Synth() with engine.Miniaudio(synth): MyApp(synth).run() print('Goodbye') # for some strange reason without this print the program sometimes hangs on close #data = engine.get_debug_data() #for x in data: print(x) 放在pip install目录下。

要在Android上进行构建,您需要一台具有setup.py的Linux机器(我在Windows Linux Subsystem 2-{{​​1}}中使用了Ubuntu,并确保对源进行git checkout在linux目录中,因为其中涉及大量编译,并且WSL中Windows目录的IO非常慢)。

pip install buildozer
wsl2
# python-for-android/recipes/engine/__init__.py
from pythonforandroid.recipe import IncludedFilesBehaviour,CppCompiledComponentsPythonRecipe
import os
import sys

class SynthRecipe(IncludedFilesBehaviour,CppCompiledComponentsPythonRecipe):
    version = 'stable'
    src_filename = "../../../engine"
    name = 'engine'

    depends = ['setuptools']

    call_hostpython_via_targetpython = False
    install_in_hostpython = True

    def get_recipe_env(self,arch):
        env = super().get_recipe_env(arch)
        env['LDFLAGS'] += ' -lc++_shared'
        return env



recipe = SynthRecipe()
$ buildozer init

现在,您可以将# in buildozer.spec change: requirements = python3,kivy,cython,py-midi,phase-engine # ... p4a.local_recipes = ./python-for-android/recipes/ 复制到Windows目录并从CMD运行$ buildozer android debug ,也可以按照我的指示进行操作,以便bin/yourapp.apk可以正常工作:Running React Native in WSL with the emulator running directly in Windows

版权声明:本文内容由互联网用户自发贡献,该文观点与技术仅代表作者本人。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如发现本站有涉嫌侵权/违法违规的内容, 请发送邮件至 dio@foxmail.com 举报,一经查实,本站将立刻删除。

相关推荐


依赖报错 idea导入项目后依赖报错,解决方案:https://blog.csdn.net/weixin_42420249/article/details/81191861 依赖版本报错:更换其他版本 无法下载依赖可参考:https://blog.csdn.net/weixin_42628809/a
错误1:代码生成器依赖和mybatis依赖冲突 启动项目时报错如下 2021-12-03 13:33:33.927 ERROR 7228 [ main] o.s.b.d.LoggingFailureAnalysisReporter : *************************** APPL
错误1:gradle项目控制台输出为乱码 # 解决方案:https://blog.csdn.net/weixin_43501566/article/details/112482302 # 在gradle-wrapper.properties 添加以下内容 org.gradle.jvmargs=-Df
错误还原:在查询的过程中,传入的workType为0时,该条件不起作用 &lt;select id=&quot;xxx&quot;&gt; SELECT di.id, di.name, di.work_type, di.updated... &lt;where&gt; &lt;if test=&qu
报错如下,gcc版本太低 ^ server.c:5346:31: 错误:‘struct redisServer’没有名为‘server_cpulist’的成员 redisSetCpuAffinity(server.server_cpulist); ^ server.c: 在函数‘hasActiveC
解决方案1 1、改项目中.idea/workspace.xml配置文件,增加dynamic.classpath参数 2、搜索PropertiesComponent,添加如下 &lt;property name=&quot;dynamic.classpath&quot; value=&quot;tru
删除根组件app.vue中的默认代码后报错:Module Error (from ./node_modules/eslint-loader/index.js): 解决方案:关闭ESlint代码检测,在项目根目录创建vue.config.js,在文件中添加 module.exports = { lin
查看spark默认的python版本 [root@master day27]# pyspark /home/software/spark-2.3.4-bin-hadoop2.7/conf/spark-env.sh: line 2: /usr/local/hadoop/bin/hadoop: No s
使用本地python环境可以成功执行 import pandas as pd import matplotlib.pyplot as plt # 设置字体 plt.rcParams[&#39;font.sans-serif&#39;] = [&#39;SimHei&#39;] # 能正确显示负号 p
错误1:Request method ‘DELETE‘ not supported 错误还原:controller层有一个接口,访问该接口时报错:Request method ‘DELETE‘ not supported 错误原因:没有接收到前端传入的参数,修改为如下 参考 错误2:cannot r
错误1:启动docker镜像时报错:Error response from daemon: driver failed programming external connectivity on endpoint quirky_allen 解决方法:重启docker -&gt; systemctl r
错误1:private field ‘xxx‘ is never assigned 按Altʾnter快捷键,选择第2项 参考:https://blog.csdn.net/shi_hong_fei_hei/article/details/88814070 错误2:启动时报错,不能找到主启动类 #
报错如下,通过源不能下载,最后警告pip需升级版本 Requirement already satisfied: pip in c:\users\ychen\appdata\local\programs\python\python310\lib\site-packages (22.0.4) Coll
错误1:maven打包报错 错误还原:使用maven打包项目时报错如下 [ERROR] Failed to execute goal org.apache.maven.plugins:maven-resources-plugin:3.2.0:resources (default-resources)
错误1:服务调用时报错 服务消费者模块assess通过openFeign调用服务提供者模块hires 如下为服务提供者模块hires的控制层接口 @RestController @RequestMapping(&quot;/hires&quot;) public class FeignControl
错误1:运行项目后报如下错误 解决方案 报错2:Failed to execute goal org.apache.maven.plugins:maven-compiler-plugin:3.8.1:compile (default-compile) on project sb 解决方案:在pom.
参考 错误原因 过滤器或拦截器在生效时,redisTemplate还没有注入 解决方案:在注入容器时就生效 @Component //项目运行时就注入Spring容器 public class RedisBean { @Resource private RedisTemplate&lt;String
使用vite构建项目报错 C:\Users\ychen\work&gt;npm init @vitejs/app @vitejs/create-app is deprecated, use npm init vite instead C:\Users\ychen\AppData\Local\npm-