- Java 双重比较
- java - 比较器与 Apache BeanComparator
- Objective-C 完成 block 导致额外的方法调用?
- database - RESTful URI 是否应该公开数据库主键?
Android 支持various audio files编码和解码。
我使用 android.media.MediaRecorder 将音频录制到音频文件中 类,但我也希望显示有关我记录的文件的信息(不是标准数据,但仍然只是文本,甚至可能由用户配置),我认为最好将此信息存储在文件中。
可能要存储的数据示例:记录的时间、记录的位置、用户的注释...
MediaRecorder 类没有任何我能找到的功能,无法添加甚至读取录制的音频文件的元数据。
我也找不到类似的类(class)。
我尝试搜索如何针对特定文件类型执行此操作,还尝试找到一个可以执行此操作的库。
我什至没有找到关于此信息的线索。
我为 MediaRecorder 类找到的唯一东西是一个名为“setLocation”的函数,它用于指示录制开始的位置(地理上),查看它的代码,我可以看到它设置参数:
public void setLocation(float latitude, float longitude) {
int latitudex10000 = (int) (latitude * 10000 + 0.5);
int longitudex10000 = (int) (longitude * 10000 + 0.5);
if (latitudex10000 > 900000 || latitudex10000 < -900000) {
String msg = "Latitude: " + latitude + " out of range.";
throw new IllegalArgumentException(msg);
}
if (longitudex10000 > 1800000 || longitudex10000 < -1800000) {
String msg = "Longitude: " + longitude + " out of range";
throw new IllegalArgumentException(msg);
}
setParameter("param-geotag-latitude=" + latitudex10000);
setParameter("param-geotag-longitude=" + longitudex10000);
}
但是 setParameter
是私有(private)的,我不确定是否可以将我想要的任何东西放入其中,即使我有办法访问它(例如反射):
private native void setParameter(String nameValuePair);
对于给定的音频/视频文件,我也不知道如何获取/修改此类信息。它不适用于 SimpleExoPlayer ,例如。
如何读取、写入和修改 Android 支持的音频文件中的元数据?
这些操作是否有任何限制?
哪些文件格式适用于此?
是否可以在录制音频时添加元数据?
是否可以通过 MediaStore 实现?但是我该怎么做这些操作呢?支持哪些文件?元数据是否保留在文件中?
编辑:好的,我已经查看了提供给我的解决方案( here , repo here ,基于 here ),它似乎运行良好。但是,它不适用于它使用的最新版本的库(org.mp4parser.isoparser:1.9.37
mp4parser 的依赖项),所以我留下这个问题有待回答:为什么它不适用于该库的最新版本吗?
代码:
object MediaMetaDataUtil {
interface PrepareBoxListener {
fun prepareBox(existingBox: Box?): Box
}
@WorkerThread
fun <T : Box> readMetadata(mediaFilePath: String, boxType: String): T? {
return try {
val isoFile = IsoFile(FileDataSourceImpl(FileInputStream(mediaFilePath).channel))
val nam = Path.getPath<T>(isoFile, "/moov[0]/udta[0]/meta[0]/ilst/$boxType")
isoFile.close()
nam
} catch (e: Exception) {
null
}
}
/**
* @param boxType the type of the box. Example is "©nam" (AppleNameBox.TYPE). More available here: https://kdenlive.org/en/project/adding-meta-data-to-mp4-video/
* @param listener used to prepare the existing or new box
* */
@WorkerThread
@Throws(IOException::class)
fun writeMetadata(mediaFilePath: String, boxType: String, listener: PrepareBoxListener) {
val videoFile = File(mediaFilePath)
if (!videoFile.exists()) {
throw FileNotFoundException("File $mediaFilePath not exists")
}
if (!videoFile.canWrite()) {
throw IllegalStateException("No write permissions to file $mediaFilePath")
}
val isoFile = IsoFile(mediaFilePath)
val moov = isoFile.getBoxes<MovieBox>(MovieBox::class.java)[0]
var freeBox = findFreeBox(moov)
val correctOffset = needsOffsetCorrection(isoFile)
val sizeBefore = moov.size
var offset: Long = 0
for (box in isoFile.boxes) {
if ("moov" == box.type) {
break
}
offset += box.size
}
// Create structure or just navigate to Apple List Box.
var userDataBox: UserDataBox? = Path.getPath(moov, "udta")
if (userDataBox == null) {
userDataBox = UserDataBox()
moov.addBox(userDataBox)
}
var metaBox: MetaBox? = Path.getPath(userDataBox, "meta")
if (metaBox == null) {
metaBox = MetaBox()
val hdlr = HandlerBox()
hdlr.handlerType = "mdir"
metaBox.addBox(hdlr)
userDataBox.addBox(metaBox)
}
var ilst: AppleItemListBox? = Path.getPath(metaBox, "ilst")
if (ilst == null) {
ilst = AppleItemListBox()
metaBox.addBox(ilst)
}
if (freeBox == null) {
freeBox = FreeBox(128 * 1024)
metaBox.addBox(freeBox)
}
// Got Apple List Box
var nam: Box? = Path.getPath(ilst, boxType)
nam = listener.prepareBox(nam)
ilst.addBox(nam)
var sizeAfter = moov.size
var diff = sizeAfter - sizeBefore
// This is the difference of before/after
// can we compensate by resizing a Free Box we have found?
if (freeBox.data.limit() > diff) {
// either shrink or grow!
freeBox.data = ByteBuffer.allocate((freeBox.data.limit() - diff).toInt())
sizeAfter = moov.size
diff = sizeAfter - sizeBefore
}
if (correctOffset && diff != 0L) {
correctChunkOffsets(moov, diff)
}
val baos = BetterByteArrayOutputStream()
moov.getBox(Channels.newChannel(baos))
isoFile.close()
val fc: FileChannel = if (diff != 0L) {
// this is not good: We have to insert bytes in the middle of the file
// and this costs time as it requires re-writing most of the file's data
splitFileAndInsert(videoFile, offset, sizeAfter - sizeBefore)
} else {
// simple overwrite of something with the file
RandomAccessFile(videoFile, "rw").channel
}
fc.position(offset)
fc.write(ByteBuffer.wrap(baos.buffer, 0, baos.size()))
fc.close()
}
@WorkerThread
@Throws(IOException::class)
fun splitFileAndInsert(f: File, pos: Long, length: Long): FileChannel {
val read = RandomAccessFile(f, "r").channel
val tmp = File.createTempFile("ChangeMetaData", "splitFileAndInsert")
val tmpWrite = RandomAccessFile(tmp, "rw").channel
read.position(pos)
tmpWrite.transferFrom(read, 0, read.size() - pos)
read.close()
val write = RandomAccessFile(f, "rw").channel
write.position(pos + length)
tmpWrite.position(0)
var transferred: Long = 0
while (true) {
transferred += tmpWrite.transferTo(0, tmpWrite.size() - transferred, write)
if (transferred == tmpWrite.size())
break
//System.out.println(transferred);
}
//System.out.println(transferred);
tmpWrite.close()
tmp.delete()
return write
}
@WorkerThread
private fun needsOffsetCorrection(isoFile: IsoFile): Boolean {
if (Path.getPath<Box>(isoFile, "moov[0]/mvex[0]") != null) {
// Fragmented files don't need a correction
return false
} else {
// no correction needed if mdat is before moov as insert into moov want change the offsets of mdat
for (box in isoFile.boxes) {
if ("moov" == box.type) {
return true
}
if ("mdat" == box.type) {
return false
}
}
throw RuntimeException("I need moov or mdat. Otherwise all this doesn't make sense")
}
}
@WorkerThread
private fun findFreeBox(c: Container): FreeBox? {
for (box in c.boxes) {
// System.err.println(box.type)
if (box is FreeBox)
return box
if (box is Container) {
val freeBox = findFreeBox(box as Container)
if (freeBox != null) {
return freeBox
}
}
}
return null
}
@WorkerThread
private fun correctChunkOffsets(movieBox: MovieBox, correction: Long) {
var chunkOffsetBoxes = Path.getPaths<ChunkOffsetBox>(movieBox as Box, "trak/mdia[0]/minf[0]/stbl[0]/stco[0]")
if (chunkOffsetBoxes.isEmpty())
chunkOffsetBoxes = Path.getPaths(movieBox as Box, "trak/mdia[0]/minf[0]/stbl[0]/st64[0]")
for (chunkOffsetBox in chunkOffsetBoxes) {
val cOffsets = chunkOffsetBox.chunkOffsets
for (i in cOffsets.indices)
cOffsets[i] += correction
}
}
private class BetterByteArrayOutputStream : ByteArrayOutputStream() {
val buffer: ByteArray
get() = buf
}
}
编写和阅读标题的示例用法:
object MediaMetaData {
@JvmStatic
@Throws(IOException::class)
fun writeTitle(mediaFilePath: String, title: String) {
MediaMetaDataUtil.writeMetadata(mediaFilePath, AppleNameBox.TYPE, object : MediaMetaDataUtil.PrepareBoxListener {
override fun prepareBox(existingBox: Box?): Box {
var nam: AppleNameBox? = existingBox as AppleNameBox?
if (nam == null)
nam = AppleNameBox()
nam.dataCountry = 0
nam.dataLanguage = 0
nam.value = title
return nam
}
})
}
@JvmStatic
fun readTitle(mediaFilePath: String): String? {
return MediaMetaDataUtil.readMetadata<AppleNameBox>(mediaFilePath, AppleNameBox.TYPE)?.value
}
}
最佳答案
似乎没有办法对 Android 中所有支持的音频格式统一执行此操作。不过,特定格式的选项有限,因此我建议坚持使用一种格式。
MP3是最受欢迎的,应该有很多选项,比如this one。 .
如果不想处理编码/解码,还有some options for a WAV format .
还有一种方法可以将元数据轨道添加到 MP4 容器中 using MediaMuxer (您可以拥有纯音频 MP4 文件)或 like this .
关于 MediaStore:here's an example (在第 318 页末尾)关于如何在使用 MediaRecorder 后向其添加元数据。尽管据我所知,数据不会记录在文件中。
更新
我编译了an example app使用 this MP4 parser library和 MediaRecorder example from SDK docs .它录制音频,将其放入 MP4 容器并添加如下字符串元数据:
MetaDataInsert cmd = new MetaDataInsert();
cmd.writeRandomMetadata(fileName, "lore ipsum tralalala");
然后在下次应用启动时读取并显示此元数据:
MetaDataRead cmd = new MetaDataRead();
String text = cmd.read(fileName);
tv.setText(text);
更新#2
关于 m4a 文件扩展名:m4a is just an alias for an mp4 file with AAC audio and has the same file format .因此,您可以使用我上面的示例应用程序,只需将文件名从 audiorecordtest.mp4
更改为 audiorecordtest.m4a
并将音频编码器从 MediaRecorder.AudioEncoder.AMR_NB
到 MediaRecorder.AudioEncoder.AAC
。
关于android - 如何获取和修改元数据以支持 Android 上的音频文件?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/36473337/
我需要您在以下方面提供帮助。近一个月来,我一直在阅读有关任务和异步的内容。 我想尝试在一个简单的 wep api 项目中实现我新获得的知识。我有以下方法,并且它们都按预期工作: public Htt
我的可执行 jar 中有一个模板文件 (.xls)。不需要在运行时我需要为这个文件创建 100 多个副本(稍后将唯一地附加)。用于获取 jar 文件中的资源 (template.xls)。我正在使用
我在查看网站的模型代码时对原型(prototype)有疑问。我知道这对 Javascript 中的继承很有用。 在这个例子中... define([], function () { "use
影响我性能的前三项操作是: 获取滚动条 获取偏移高度 Ext.getStyle 为了解释我的应用程序中发生了什么:我有一个网格,其中有一列在每个单元格中呈现网格。当我几乎对网格的内容做任何事情时,它运
我正在使用以下函数来获取 URL 参数。 function gup(name, url) { name = name.replace(/[\[]/, '\\\[').replace(/[\]]/,
我最近一直在使用 sysctl 来做很多事情,现在我使用 HW_MACHINE_ARCH 变量。我正在使用以下代码。请注意,当我尝试获取其他变量 HW_MACHINE 时,此代码可以完美运行。我还认为
关闭。这个问题不符合Stack Overflow guidelines .它目前不接受答案。 关闭 9 年前。 要求提供代码的问题必须表现出对所解决问题的最低限度的理解。包括尝试过的解决方案、为什么
由于使用 main-bower-files 作为使用 Gulp 的编译任务的一部分,我无法使用 node_modules 中的 webpack 来require 模块code> dir 因为我会弄乱当
关闭。这个问题需要更多focused .它目前不接受答案。 想改进这个问题吗? 更新问题,使其只关注一个问题 editing this post . 关闭 5 年前。 Improve this qu
我使用 Gridlayout 在一行中放置 4 个元素。首先,我有一个 JPanel,一切正常。对于行数变大并且我必须能够向下滚动的情况,我对其进行了一些更改。现在我的 JPanel 上添加了一个 J
由于以下原因,我想将 VolumeId 的值保存在变量中: #!/usr/bin/env python import boto3 import json import argparse import
我正在将 MSAL 版本 1.x 更新为 MSAL-browser 的 Angular 。所以我正在尝试从版本 1.x 迁移到 2.X.I 能够成功替换代码并且工作正常。但是我遇到了 acquireT
我知道有很多关于此的问题,例如 Getting daily averages with pandas和 How get monthly mean in pandas using groupby但我遇到
This is the query string that I am receiving in URL. Output url: /demo/analysis/test?startDate=Sat+
我正在尝试使用 javascript 中的以下代码访问 Geoserver 层 var gkvrtWmsSource =new ol.source.ImageWMS({ u
API 需要一个包含授权代码的 header 。这就是我到目前为止所拥有的: var fullUrl = 'https://api.ecobee.com/1/thermostat?json=\{"s
如何获取文件中的最后一个字符,如果是某个字符,则删除它而不将整个文件加载到内存中? 这就是我目前所拥有的。 using (var fileStream = new FileStream("file.t
我是这个社区的新手,想出了我的第一个问题。 我正在使用 JSP,我成功地创建了 JSP-Sites,它正在使用jsp:setParameter 和 jsp:getParameter 具有单个字符串。
在回答 StoreStore reordering happens when compiling C++ for x86 @Peter Cordes 写过 For Acquire/Release se
我有一个函数,我们将其命名为 X1,它返回变量 Y。该函数在操作 .on("focusout", X1) 中使用。如何获取变量Y?执行.on后X1的结果? 最佳答案 您可以更改 Y 的范围以使其位于函
我是一名优秀的程序员,十分优秀!