低代码到底是不是行业毒瘤?一线大厂怎么做的?戳此了解>>> 了解详情
写点什么

用模拟器实现视频流的音画分离

2021 年 3 月 12 日

用模拟器实现视频流的音画分离

在 360 开测平台上, 对内的业务中, 需要对⾳视频进⾏检测,  ⾳频的抽取成为⼀个难题。


对比方案

在 Android ⼿机中,实现⾳频内录有以下⼏种⽅式:


1. 硬件⽀持 (⻨克⻛⾳频输出在转换为输⼊):  ⽅案可⾏, 但是需要⼀定的成本, ⽽且不适合第三⽅ APK。

2. root Android ⼿机, 伪装为系统应⽤:   (未尝试) 360 开测平台上的真机不可能把⼿机 root, ⻛险太⼤。

3. Android 9 以上系统⼿机: 只能⽆忧⽆虑的录制系统声⾳, 需要第三⽅的 APK⽀持, ⽽且录制效果很差。

4. 模拟器内录: 伪装系统 APK 在模拟器中录制, 应⽤崩溃, 模拟器不⽀持。

5. PC 录制模拟器外放⾳量: ⾳频混淆, 不易拆分。

6. 模拟器⾃⼰录制:  ⾄今为⽌, 发现逍遥模拟器可以多个模拟器同时录制, 互不⼲扰。


今天我们就说⼀下, 怎么使⽤逍遥模拟器来抽取⼿机中的⾳频⽂件(包括第三⽅的 APK 和系统的 APK)。

方案实现

获取模拟器对应关系

在逍遥模拟器安装路径中,  可以看到 MemuHyperv VMs ⽂件夹,  打开可以看到当前我们所创建的所有模拟器, 在 MEmu_1.memu   或者  MEmu_1.memu-prev ⽂件中, 存储了模拟器的配置信息, 信息存储是按照 XML 的格式来存储的, 我们直接解析当前的 XML⽂件。


def parse_file(filepath):    """    解析 MEmu.memu  xml 文件信息, 获取信息    :param filepath:    :return:    """    infodict = {}    for root, dirs, files in os.walk(filepath):        for f in files:            if f.startswith("MEmu") and f.endswith(".memu"):                path = os.path.join(root, f)                dom = parse(path)                data = dom.documentElement                Machines = data.getElementsByTagName('Machine')                for Machine in Machines:                    Machine_name = Machine.getAttribute('name')                    Machine_index = getMachineIndex(Machine_name)                    break                Forwardings = data.getElementsByTagName('Forwarding')                for host in Forwardings:                    if host.getAttribute('name') == "ADB":                        hostport = host.getAttribute('hostport')                        break                infodict[Machine_index] = [Machine_name, hostport]    return infodict
复制代码


我们在⽂件中, 分别获取  标签中的 name 属性, 标签中的 hostport 属性。


name 是指当前模拟器的名字, Forwarding 是指当前模拟器的 tid, tid 的值和 adb devices 命令获取的值是相同的, 我们可以根据这些信息, 来分别对应到各个模拟器上。



在逍遥模拟器的官⽅命令中, 有这么⼀条命令:


memuc  listvms --running # 就是获取我们当前正在运⾏的模拟器的⼀些信息输出参数顺序: 模拟器索引, 标题(模拟器的⻚⾯标题, 和我们上述获取的不同), 顶层窗⼝的句柄, 是否进⼊Androi, 进程pid 信息, 模拟器磁盘占⽤的信息 
复制代码

最终, 我们合并后的集合:


{u'1': ['1', 'xxx - 1', '2950304', '1', '5128', u'MEmu_1', u'21513'], u'3': ['3', 'xxx - 3', '19073560', '1', '16948', u'MEmu_3', u'21533'], u'2': ['2', 'xxx - 2', '5573082', '1', '10924', u'MEmu_2', u'21523'], u'5': ['5', 'xxx - 5', '10750466', '1', '10112', u'MEmu_5', u'21553'], u'4': ['4', 'xxx - 4', '8063248', '1', '2908', u'MEmu_4', u'21543']}
复制代码

开始录屏操作

win32gui.ShowWindow(hwnd, 1) # hwnd 句柄win32gui.SetForegroundWindow(hwnd)win32api.keybd_event(17, 0, 0, 0)  # ctrl 键码是17win32api.keybd_event(116, 0, 0, 0)  # f5 win32api.keybd_event(17, 0, win32con.KEYEVENTF_KEYUP, 0) win32api.keybd_event(116, 0, win32con.KEYEVENTF_KEYUP, 0)
复制代码


结束屏幕录制


win32gui.ShowWindow(hwnd, 1) win32gui.SetForegroundWindow(hwnd)win32api.keybd_event(17, 0, 0, 0)  # ctrl 键码是17win32api.keybd_event(117, 0, 0, 0)  # f6 win32api.keybd_event(17, 0, win32con.KEYEVENTF_KEYUP, 0) win32api.keybd_event(117, 0, win32con.KEYEVENTF_KEYUP, 0)
复制代码


就是简单的进⾏ ctrl + F5 和 ctrl + F6 的操作, 在操作的时候, 需要设置当前模拟器的焦点, 也就是我们需要把当前模拟器置顶操作, 操作的句柄就是在第⼀步的信息中

坑:  在 windows 系统下, python 需要以管理员的权限运⾏, 或者给 python 赋予完全控制权限, 不然, 模拟器的窗⼝置顶操作会失败。

音画分离

我们在录屏结束后, 采⽤ffmpeg 来进⾏⾳画分离



ffmpeg -i 视频路径 -ar 16000 -vn 音频输出路径
复制代码



这样, 我们就获取到当前模拟器的⾳频⽂件了, 最后⽂件输出为 wav ⽂件, 通过这个⽂件, 就可以对⾳频⽂件进⾏⾳频质量检测

视频文件对应模拟器

这个是本⽂最⼤的坑,且听我详细说⼀下:


⽐⽅说, 我们开启了 5 个模拟器, 上⾯⼀些图, 都是开 5 个模拟器获取到的信息, 5 个模拟器在同时⼯作的时候, 深坑就来了。


坑 1:  录制后的视频命名规范为 %Y%m%d%H%M%S, 最⼩区分度为秒, 这就可能会造成视频名字会重复, ⽂件覆盖, 造成最后的分离的⾳频缺失


解决⽅案: 在操作模拟器的时候, 需要给 1 秒以上的间隔, 保证当前的视频⽂件不会重复


坑 2:  没有视频⽂件和模拟器的对应关系


解决⽅案: 在每个模拟器开始录屏前, 获取当前时间, 并记录下来, 基本就能和模拟器对应起来


坑 3: 模拟器录制的视频⽂件的名字和我们⾃⼰定义的视频⽂件的名字有出⼊, 会有⽂件找不到的错误

解决⽅案: 我们定义的时间和模拟器开始录制的时间稍微有些区别,  ⼤部分都是 1 秒钟的差别, 我们采⽤如下⽅式来寻找⽂件, 可能还会有点缺陷, 需要在研究下


if FileUtils.isExists(videopath):        filepath_no_ext = os.path.splitext(videopath)[0]        return filepath_no_ext + ".wav"
videopath = root_path + fileNameAddOne(taskdata[key][1]) + ".mp4" if FileUtils.isExists(videopath): updatetime(fileNameAddOne(taskdata[key][1])) filepath_no_ext = os.path.splitext(videopath)[0] return filepath_no_ext + ".wav"
videopath = root_path + fileNamesubOne(taskdata[key][1]) + ".mp4" if FileUtils.isExists(videopath): updatetime(fileNamesubOne(taskdata[key][1])) filepath_no_ext = os.path.splitext(videopath)[0] return filepath_no_ext + ".wav"
复制代码


本文转载自:360 技术(ID:qihoo_tech)

原文链接:用模拟器实现视频流的音画分离


2021 年 3 月 12 日 13:001029

评论

发布
暂无评论
发现更多内容

【STM32】PWM 输出 (标准库)

AXYZdong

硬件 stm32 2月春节不断更

3.Fiber(我是在内存中的dom)

全栈潇晨

React React Hooks react源码

华为云FusionInsight MRS在金融行业存算分离的实践

华为云开发者社区

大数据 金融 华为云 存算分离 FusionInsight MRS

阿里云大佬爆裂推荐“redis全新手册”,内容即精华

比伯

Java redis 程序员 架构 程序人生

日记 2021年2月18日(周四)

Changing Lin

2月春节不断更

gradle中的增量构建

程序那些事

maven Gradle 程序那些事 构建工具

IDEA插件:快速删除Java代码中的注释

xiaoxi666

代码注释 Java 8 JavaParser

门诊数字化:患者信息识别方式

boshi

医疗 数字化基础 七日更

面试的季节到了,老哥确定不来复习下数据结构吗

Silently9527

面试 数据结构与算法

up主周月纪念日前夜总结文

Kylin

视频创作 自媒体 up主 周月纪念日

EternalWallet为您提供快速、便捷、低价的国际汇款服务

Geek_c610c0

一维数组的动态和

小马哥

算法

区块链挖矿系统APP开发|区块链挖矿软件开发(现成)

v16629866266

什么是阻抗?

不脱发的程序猿

阻抗 电路设计 电子元器件

如何 1 天快速集成自己的“Clubhouse”?

融云 RongCloud

音视频 clubhouse 语音社交 融云

第四章作业-编写一个用例文档

秦挺

【LeetCode】重塑矩阵Java题解

HQ数字卡

算法 LeetCode 2月春节不断更

【函数计算实践】nodejs初探示例——本地mac环境

程序员架构进阶

架构 nodejs 函数计算 七日更 2月春节不断更

LeetCode题解:1091. 二进制矩阵中的最短路径,BFS,JavaScript,详细注释

Lee Chen

算法 LeetCode 前端进阶训练营

Elasticsearch mapping 复杂数据类型

escray

elastic 七日更 死磕Elasticsearch 60天通过Elastic认证考试 2月春节不断更

用例文档

三生赤水

厉害了!这群95后正在用三维成像技术让科幻变成现实

华为云开发者社区

视频 华为云 三维 裸眼 光学

心理声学基础

行者AI

心理 音乐

【STM32】EXTI---外部中断/事件控制器

AXYZdong

硬件 stm32 2月春节不断更

字幕组时代落幕,翻译的未来可能是?

字节跳动技术团队

第 4 周作业

老元宵

话题讨论 | 如何使用“网站SEO”,让网站排在最前面?

魔王哪吒

前端 后端 话题讨论 SEO 2月春节不断更

哲少荐书:鞋狗

Jackey

书籍推荐

端口隔离和VLAN的区别

算法从有序数组中移除重复的数据,AI学习资源2020 John 易筋 ARTS 打卡 Week 38

John(易筋)

ARTS 打卡计划 ai youbute学习资源

ElasticSearch.04 - 基础操作

insight

elasticsearch 2月春节不断更

2021 ThoughtWorks 技术雷达峰会

2021 ThoughtWorks 技术雷达峰会

用模拟器实现视频流的音画分离-InfoQ