- html - 出于某种原因,IE8 对我的 Sass 文件中继承的 html5 CSS 不友好?
- JMeter 在响应断言中使用 span 标签的问题
- html - 在 :hover and :active? 上具有不同效果的 CSS 动画
- html - 相对于居中的 html 内容固定的 CSS 重复背景?
我们正在尝试将 Android 设备中的视频和音频保存到加密文件中。我们当前的实现通过 MediaEncoder 类通过管道传输麦克风和摄像头的输出。当数据从 MediaEncoder 输出时,我们正在加密并将字节缓冲区的内容写入磁盘。但是,当尝试使用 FFMPEG 将文件重新拼接在一起时,这种方法有效,我们注意到这两个流似乎在流中间某处不同步。这种方法似乎丢失了许多重要的元数据,特别是演示时间戳和帧速率数据,因为 ffmpeg 必须做一些猜测工作才能混合文件。
是否有技术可以在不使用 MediaMuxer 的情况下保持这些流同步?视频使用 H.264 编码,音频使用 AAC。
其他方法:我们尝试使用 MediaMuxer 将输出数据多路复用到一个文件中,但我们的用例要求我们在将数据字节保存到磁盘之前对其进行加密,这消除了使用默认构造函数的可能性。
此外,我们尝试使用新添加的 (API 26) 构造函数,该构造函数采用 FileDescriptor 代替,并指向包装加密文档 (https://android.googlesource.com/platform/development/+/master/samples/Vault/src/com/example/android/vault/EncryptedDocument.java) 的 ParcelFileDescriptor。然而,这种方法导致了 native 层的崩溃,我们认为这可能与源代码 (https://android.googlesource.com/platform/frameworks/base.git/+/master/media/java/android/media/MediaMuxer.java#353) 中关于 native 编写器试图内存映射输出文件的评论有关。
import android.graphics.YuvImage
import android.media.MediaCodec
import android.media.MediaCodecInfo
import android.media.MediaFormat
import android.media.MediaMuxer
import com.callyo.video_10_21.Utils.YuvImageUtils.convertNV21toYUV420Planar
import java.io.FileDescriptor
import java.util.*
import java.util.concurrent.atomic.AtomicReference
import kotlin.properties.Delegates
class VideoEncoderProcessor(
private val fileDescriptor: FileDescriptor,
private val width: Int,
private val height: Int,
private val frameRate: Int
): MediaCodec.Callback() {
private lateinit var videoFormat: MediaFormat
private var trackIndex by Delegates.notNull<Int>()
private var mediaMuxer: MediaMuxer
private val mediaCodec = createEncoder()
private val pendingVideoEncoderInputBufferIndices = AtomicReference<LinkedList<Int>>(LinkedList())
companion object {
private const val VIDEO_FORMAT = "video/avc"
}
init {
mediaMuxer = MediaMuxer(fileDescriptor, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
mediaCodec.setCallback(this)
mediaCodec.start()
}
private fun createEncoder(): MediaCodec {
videoFormat = MediaFormat.createVideoFormat(VIDEO_FORMAT, width, height).apply {
setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)
setInteger(MediaFormat.KEY_BIT_RATE, width * height * 5)
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
}
return MediaCodec.createEncoderByType(VIDEO_FORMAT).apply {
configure(videoFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)
}
}
override fun onInputBufferAvailable(codec: MediaCodec, index: Int) {
// logic for handling stream end omitted for clarity
/* Video frames come in asynchronously from input buffer availability
* so we need to keep track of available buffers in queue */
pendingVideoEncoderInputBufferIndices.get().add(index)
}
override fun onError(codec: MediaCodec, e: MediaCodec.CodecException) {}
override fun onOutputFormatChanged(codec: MediaCodec, format: MediaFormat) {
trackIndex = mediaMuxer.addTrack(format)
mediaMuxer.start()
}
override fun onOutputBufferAvailable(codec: MediaCodec, index: Int, bufferInfo: MediaCodec.BufferInfo) {
val buffer = mediaCodec.getOutputBuffer(index)
buffer?.apply {
if (bufferInfo.size != 0) {
limit(bufferInfo.offset + bufferInfo.size)
rewind()
mediaMuxer.writeSampleData(trackIndex, this, bufferInfo)
}
}
mediaCodec.releaseOutputBuffer(index, false)
if (bufferInfo.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
mediaCodec.stop()
mediaCodec.release()
mediaMuxer.stop()
mediaMuxer.release()
}
}
// Public method that receives raw unencoded video data
fun encode(yuvImage: YuvImage) {
// logic for handling stream end omitted for clarity
pendingVideoEncoderInputBufferIndices.get().poll()?.let { index ->
val buffer = mediaCodec.getInputBuffer(index)
buffer?.clear()
// converting frame to correct color format
val input =
yuvImage.convertNV21toYUV420Planar(ByteArray(yuvImage.yuvData.size), yuvImage.width, yuvImage.height)
buffer?.put(input)
buffer?.let {
mediaCodec.queueInputBuffer(index, 0, input.size, System.nanoTime() / 1000, 0)
}
}
}
}
附加信息:我正在使用 MediaCodec.Callback() ( https://developer.android.com/reference/kotlin/android/media/MediaCodec.Callback?hl=en ) 异步处理编码。
最佳答案
我将引用以下问答:sync audio and video with mediacodec and mediamuxer
由于信息丢失:
in order to sync audio and video you have to "calculate the number of audio samples that should play for each frame of video"
作者继续并提供了一个例子,例如
It depends on the sample rate and the frame rate:
at 24fps and 48000Hz every frame is long (48000hz/24fps)= 2000 sample
at 25 fps and 48000Hz: (48000hz/25fps)= 1920 sample
看看下面这个混合视频和音频文件的示例,他们在其中设置样本大小并组合视频和音频(来自:https://github.com/Docile-Alligator/Infinity-For-Reddit/blob/61c5682b06fb3739a9f980700e6602ae0f39d5a2/app/src/main/java/ml/docilealligator/infinityforreddit/services/DownloadRedditVideoService.java#L506)
private boolean muxVideoAndAudio(String videoFilePath, String audioFilePath, String outputFilePath) {
try {
File file = new File(outputFilePath);
file.createNewFile();
MediaExtractor videoExtractor = new MediaExtractor();
videoExtractor.setDataSource(videoFilePath);
MediaExtractor audioExtractor = new MediaExtractor();
audioExtractor.setDataSource(audioFilePath);
MediaMuxer muxer = new MediaMuxer(outputFilePath, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4);
videoExtractor.selectTrack(0);
MediaFormat videoFormat = videoExtractor.getTrackFormat(0);
int videoTrack = muxer.addTrack(videoFormat);
audioExtractor.selectTrack(0);
MediaFormat audioFormat = audioExtractor.getTrackFormat(0);
int audioTrack = muxer.addTrack(audioFormat);
boolean sawEOS = false;
int offset = 100;
int sampleSize = 2048 * 1024;
ByteBuffer videoBuf = ByteBuffer.allocate(sampleSize);
ByteBuffer audioBuf = ByteBuffer.allocate(sampleSize);
MediaCodec.BufferInfo videoBufferInfo = new MediaCodec.BufferInfo();
MediaCodec.BufferInfo audioBufferInfo = new MediaCodec.BufferInfo();
videoExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
audioExtractor.seekTo(0, MediaExtractor.SEEK_TO_CLOSEST_SYNC);
muxer.start();
while (!sawEOS) {
videoBufferInfo.offset = offset;
videoBufferInfo.size = videoExtractor.readSampleData(videoBuf, offset);
if (videoBufferInfo.size < 0 || audioBufferInfo.size < 0) {
sawEOS = true;
videoBufferInfo.size = 0;
} else {
videoBufferInfo.presentationTimeUs = videoExtractor.getSampleTime();
videoBufferInfo.flags = videoExtractor.getSampleFlags();
muxer.writeSampleData(videoTrack, videoBuf, videoBufferInfo);
videoExtractor.advance();
}
}
boolean sawEOS2 = false;
while (!sawEOS2) {
audioBufferInfo.offset = offset;
audioBufferInfo.size = audioExtractor.readSampleData(audioBuf, offset);
if (videoBufferInfo.size < 0 || audioBufferInfo.size < 0) {
sawEOS2 = true;
audioBufferInfo.size = 0;
} else {
audioBufferInfo.presentationTimeUs = audioExtractor.getSampleTime();
audioBufferInfo.flags = audioExtractor.getSampleFlags();
muxer.writeSampleData(audioTrack, audioBuf, audioBufferInfo);
audioExtractor.advance();
}
}
try {
muxer.stop();
muxer.release();
} catch (IllegalStateException ignore) {}
} catch (IOException e) {
e.printStackTrace();
return false;
}
return true;
}
看看@以下页面:https://sisik.eu/blog/android/media/mix-audio-into-video
从那里开始,他们在部分中有一个很好的示例:使用 MediaMuxer 将帧混合到 MP4
,您可以使用它来将文件拼接在一起。
从那里:
In my case, I want to get input from MPEG-4 video and from AAC/M4A audio file, > and mux both inputs into one MPEG-4 output video file. To accomplish that, Icreated the following mux() method
fun mux(audioFile: String, videoFile: String, outFile: String) {
// Init extractors which will get encoded frames
val videoExtractor = MediaExtractor()
videoExtractor.setDataSource(videoFile)
videoExtractor.selectTrack(0) // Assuming only one track per file. Adjust code if this is not the case.
val videoFormat = videoExtractor.getTrackFormat(0)
val audioExtractor = MediaExtractor()
audioExtractor.setDataSource(audioFile)
audioExtractor.selectTrack(0) // Assuming only one track per file. Adjust code if this is not the case.
val audioFormat = audioExtractor.getTrackFormat(0)
// Init muxer
val muxer = MediaMuxer(outFile, MediaMuxer.OutputFormat.MUXER_OUTPUT_MPEG_4)
val videoIndex = muxer.addTrack(videoFormat)
val audioIndex = muxer.addTrack(audioFormat)
muxer.start()
// Prepare buffer for copying
val maxChunkSize = 1024 * 1024
val buffer = ByteBuffer.allocate(maxChunkSize)
val bufferInfo = MediaCodec.BufferInfo()
// Copy Video
while (true) {
val chunkSize = videoExtractor.readSampleData(buffer, 0)
if (chunkSize > 0) {
bufferInfo.presentationTimeUs = videoExtractor.sampleTime
bufferInfo.flags = videoExtractor.sampleFlags
bufferInfo.size = chunkSize
muxer.writeSampleData(videoIndex, buffer, bufferInfo)
videoExtractor.advance()
} else {
break
}
}
// Copy audio
while (true) {
val chunkSize = audioExtractor.readSampleData(buffer, 0)
if (chunkSize >= 0) {
bufferInfo.presentationTimeUs = audioExtractor.sampleTime
bufferInfo.flags = audioExtractor.sampleFlags
bufferInfo.size = chunkSize
muxer.writeSampleData(audioIndex, buffer, bufferInfo)
audioExtractor.advance()
} else {
break
}
}
// Cleanup
muxer.stop()
muxer.release()
videoExtractor.release()
audioExtractor.release()
}
根据您的评论,我认为主要问题是 fileDescriptor
。具体来说,它们仅将 RandomAccessFile 用于文件描述符,但 native 接口(interface)是进行读取的接口(interface)。
那么我有一个建议,也许您应该考虑使用 内存中
而不是基于文件的 FileDescriptor
。
因此,读取加密文件并在内存中对其进行解密,然后将这些字节转换为新的内存中文件描述符。将该内存中的文件描述符提供给多路复用器,看看会发生什么。
关于这个问题有一个很好的答案,他们使用安全的私有(private)应用专用套接字来创建文件描述符,请参阅:Create an in-memory FileDescriptor
从以下位置开始具体检查该答案的第二部分:
A better, but more complicated solution is to create a socket in thefilesystem namespace.Reference: https://stackoverflow.com/a/62651005/1688441
所以更详细:
如回答所述:
This does create a file on the filesystem, but none of the data that passes through the socket is ever written to disk, it is entirelyin-memory. The file is just a name that represents the socket, similarto the files in /dev that represent devices. Because the socket isaccessed through the filesystem, it is subject to the usual filesystempermissions, so it is easy to restrict access to the socket by placingthe socket in your app's private data area.
Since this technique creates a file on the filesystem, it would be agood idea to delete the file after you're done, and also perhaps tocheck for and clean up old sockets every so often, in case your appcrashes and leaves old files laying around.
关于android - 我如何在 android 上录制、加密(在内存中)和多路复用音频和视频而不会使文件不同步?,我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/65957947/
粗略地说,单向数据绑定(bind)只是与 ng-model 绑定(bind)。当涉及 Controller 时,在页面内和 2-way 内。有人可以向我解释这个概念,以便我真正了解如何看待它吗?还有什
我想知道是否有任何替代 2 向 SSL 的方法。 2 向 SSL 是确保客户端和服务器可信通信的唯一选择吗?我有一个自签名证书供我的客户使用,我能否将自签名证书重新用于 2 种 SSL 方式,还是应该
如果是这样,你如何设置认证证书,你需要什么文件?是 .pfx 吗?您将如何在浏览器中安装它?一直试图通过浏览器测试 2 路 ssl。我有一个网络服务,尝试连接时总是返回认证身份验证失败。 最佳答案 扩
我希望能够对 XHTML 文档进行三向合并: 从文档的一些原始副本开始 一个用户编辑原始文档的副本 另一个用户编辑原始文档的单独副本 需要一个工具来合并(自动和/或可视化)两个用户所做的更改。 注意:
我有 4 张 table : ad (id, ...) website (id, title, URL, ...) space (id, website_id, ...) ad_space_count
我在 java 中有一个无状态服务,部署在 tomcat 网络服务器中,我还配置了 2 路 ssl 验证。到目前为止,一切正常。当我有一个新客户端时,我只需要将新客户端证书放入我的 trustore
我已经创建了一个带有证书的信任库和带有私钥的 keystore 。我已经放置了以下代码,加载了 trsustore 管理器和 keystore 管理器,然后创建了 SSL 上下文的实例。 每当我向网络
如果我在仅服务器身份验证中正确理解 SSL/TLS,握手后,服务器会向客户端发送它的公钥和由 CA 签名的数字签名证书。如果客户端有这个 CA 的公钥,它就可以解密证书并与服务器建立信任。如果它不信任
我有 Nginx,它使用双向 TLS 代理从客户端到 IBM DataPower 的请求。 从 Nginx 向 IBM DP 发送消息时出现错误:sll server (SERVER) ssl pee
我刚刚开始了一个项目,让我的雇主成为一个管理软件。我有一个琐碎但可能很简单的查询,我似乎找不到任何相关信息。 在对象之间建立“具有”关系的两种方式是否谨慎/良好做法。例如,Client 对象“有一个”
我在设置双向 SSL 身份验证时遇到问题。 我需要从 wso2 企业集成商访问 HTTPS 端点。 服务提供商给了我一个 pfx keystore ,其中包含我必须提供给服务器的证书和私钥。 我在我的
我正在为小型 PoC 构建 AWS Lambda 服务。 PoC 中的流程是: 通过 POST 获取(文本)输入, 执行小字符串操作 + 将操纵值存储到 DynamoDB 中,然后 通过 HTTP P
我的任务是在 Java 上下文中实现双向 TLS。我找到了一个示例 ( https://www.opencodez.com/java/implement-2-way-authentication-us
我正在尝试测试一个非常简单的双向 IM 应用程序。客户端在 android 上,服务器在我的 PC(java)上。我已经在 PC 到 PC 之间用 java 测试了这个应用程序,它工作正常。 但是在我
我有 java web 服务支持2-way ssl auth。所以我有客户端 keystore (client.p12),服务器证书在受信任的存储区中,服务器 keystore 中的客户端证书在受信任
通过 HTTPS 使用 Web 服务 我们有一个我们正在使用的网络服务。 Webservice 可以在 HTTP 和 HTTPS 协议(protocol)上运行。使用 HTTP 没问题,但如何使用 H
我在 Node.js 上有一个后端服务器,我正在尝试在 Nginx 和这个后端服务器之间设置 2 路 SSL。 但是我得到一个错误:2015/11/02 06:51:02 [错误] 12840#128
我一直在尝试连接到启用了 2 路 SSL 的服务端点。我正在使用 Spring resttemplate。我已将证书添加到 keystore 中,但出现以下错误: >org.springframewo
从 CherryPy 3.0 开始,只需指向服务器证书和私钥即可启用单向 SSL,如下所示: import cherrypy class HelloWorld(object): def ind
这个问题来自:MySQL Number of Days inside a DateRange, inside a month (Booking Table) 我有一个包含以下数据的表: CREATE
我是一名优秀的程序员,十分优秀!