AMediaCodec_queueInputBuffer ist blockiertC++

Programme in C++. Entwicklerforum
Anonymous
 AMediaCodec_queueInputBuffer ist blockiert

Post by Anonymous »

AMediaCodec_queueInputBuffer ist blockiert
Titel
Android NDK MediaCodec AVC-Encoder blockiert/reagiert nicht mehr nach Übermittlung des ersten Eingabepuffers im asynchronen Modus
Problembeschreibung
Ich implementieren einen H.264-Video-Encoder mithilfe der Android NDK MediaCodec C-API im asynchronen Rückruf Modus (AMediaCodec_setAsyncNotifyCallback).
Ich stoße auf ein kritisches Problem: Nach der erfolgreichen Übermittlung des allerersten Datenrahmens innerhalb des onAsyncInputAvailable-Rückrufs werden sowohl nachfolgende Eingaberückrufe (onAsyncInputAvailable) als auch alle erwarteten Ausgaberückrufe (onAsyncOutputAvailable) nicht mehr ausgelöst. Das Programm scheint dann zu hängen oder reagiert für einen längeren Zeitraum nicht mehr.
Ich benötige Anleitung zur korrekten Implementierung des Producer-Consumer-Modells im asynchronen NDK-Modus, um diesen Deadlock/diese Blockade zu verhindern und eine zuverlässige EOS-Signalisierung sicherzustellen.
C++-Header

Code: Select all

//
// Created by 29051 on 2025/11/22.
//

#ifndef OKHTTPLEARN_LEARN001_HPP
#define OKHTTPLEARN_LEARN001_HPP

#include 
#include 
#include 
#include 
#include 

#include 
#include 
#include 
#include 
#include 

#include "logging.hpp"

namespace learn01 {
void hello001();
class Encoder{
private:
AMediaCodec *mediaCodec = nullptr;
AMediaFormat *mediaFormat = nullptr;
std::ifstream *yuvFile = nullptr;
std::ofstream *h264File = nullptr;
typedef media_status_t (*AMediaCodec_setAsyncNotifyCallback_Type)(
AMediaCodec*, AMediaCodecOnAsyncNotifyCallback, void *
);
AMediaCodec_setAsyncNotifyCallback_Type pSetAsyncNotifyCallback = nullptr;
void* libHandle;
public:
Encoder();
~Encoder();
uint64_t getMicroseconds();
void load_media_codec_async_function();
};
}
#endif //OKHTTPLEARN_LEARN001_HPP

C++-Quelldatei

Code: Select all

//
// Created by 29051 on 2025/11/22.
//
#include 
#include "learn001.hpp"

static const char * const TAG = "learn001";

namespace learn01 {
void hello001(){
logger::info(TAG, "learn01 learn001...");
}
/**
* ffmpeg -i video.mp4 -codec copy -an output.h264
* ffmpeg -i video.mp4 -codec copy -vn -sn output.aac
* ffmpeg -i output.h264 -pix_fmt yuv420p output.yuv
* ffplay -f rawvideo -pixel_format yuv420p -video_size 720x1280 output.yuv
* adb push output.yuv /data/data/io.github.okhttplearn/files
* https://zhuanlan.zhihu.com/p/71928833
* https://www.yuque.com/keith-an9fr/aab7xp/cy9suo
* https://www.cnblogs.com/linuxAndMcu/p/14533228.html
* https://ffmpeg.xianwaizhiyin.net/demuxer/h264_format.html
* https://blogs.hdvr.top/2021/09/22/%E9%9F%B3%E8%A7%86%E9%A2%91%E4%B9%8B-H-264%E6%A0%BC%E5%BC%8F/
*/
uint64_t Encoder::getMicroseconds(){
const std::chrono::time_point now = std::chrono::system_clock::now();
const std::chrono::duration duration = now.time_since_epoch();
const std::chrono::duration microseconds = std::chrono::duration_cast(duration);
return microseconds.count();
}
void Encoder::load_media_codec_async_function(){
this -> libHandle = dlopen("libmediandk.so", RTLD_NOW);
if (libHandle) {
pSetAsyncNotifyCallback = (AMediaCodec_setAsyncNotifyCallback_Type) dlsym(
libHandle,
"AMediaCodec_setAsyncNotifyCallback"
);
// 注意:不应该 dlclose,因为它可能被其他 NDK API 使用
}
}
Encoder::Encoder() {
load_media_codec_async_function();
logger::info(TAG, "主线程 id: %d", std::this_thread::get_id());
this -> yuvFile = new std::ifstream("/data/data/io.github.okhttplearn/files/output.yuv", std::ios::binary);
this ->  h264File = new std::ofstream("/data/data/io.github.okhttplearn/files/output.h264", std::ios::binary);
if(!yuvFile->is_open() || !h264File->is_open()){
throw std::runtime_error("文件打开 is null");
}
this->mediaFormat = AMediaFormat_new();
if (mediaFormat == nullptr){
throw std::runtime_error("mediaFormat is null");
}
AMediaFormat_setString(mediaFormat, AMEDIAFORMAT_KEY_MIME, "video/avc");
AMediaFormat_setInt32(mediaFormat, AMEDIAFORMAT_KEY_WIDTH, 720);
AMediaFormat_setInt32(mediaFormat, AMEDIAFORMAT_KEY_HEIGHT, 1280);
AMediaFormat_setInt32(mediaFormat, AMEDIAFORMAT_KEY_FRAME_RATE, 30);
AMediaFormat_setInt32(mediaFormat, AMEDIAFORMAT_KEY_BIT_RATE, 200'0000);
AMediaFormat_setInt32(mediaFormat, AMEDIAFORMAT_KEY_COLOR_FORMAT, 19);
AMediaFormat_setInt32(mediaFormat, AMEDIAFORMAT_KEY_I_FRAME_INTERVAL, 1);

this->mediaCodec = AMediaCodec_createEncoderByType("video/avc");
if (mediaCodec == nullptr){
throw std::runtime_error("mediaCodec is null");
}

auto status = AMediaCodec_configure(mediaCodec, mediaFormat, nullptr, nullptr, AMEDIACODEC_CONFIGURE_FLAG_ENCODE);
if (status != AMEDIA_OK){
throw std::runtime_error("AMediaCodec_configure status != A_MEDIA_OK");
}

const auto callback =  AMediaCodecOnAsyncNotifyCallback{
.onAsyncInputAvailable = [](AMediaCodec *codec, void *userdata, int32_t index) -> void {
const auto encoder = reinterpret_cast(userdata);
// 如何实现回调转协程同步?
size_t outSize = 0;
uint8_t *buffer = AMediaCodec_getInputBuffer(codec, index, &outSize);
const int frameSize = 720 * 1280 * 3 / 2;
if (outSize == frameSize){
encoder->yuvFile->read(reinterpret_cast(buffer), frameSize);
const auto gCount = encoder->yuvFile->gcount();
logger::info(TAG, "onAsyncInputAvailable outSize: %d, frameSize: %d, gCount: %d, threadId: %d, timestamp: %lld", outSize, frameSize, gCount, std::this_thread::get_id(), encoder->getMicroseconds() / 1000);
const auto status = AMediaCodec_queueInputBuffer(codec, index, 0, frameSize, encoder->getMicroseconds() / 1000, 0);
if (status != AMEDIA_OK){
logger::error(TAG, "AMediaCodec_queueInputBuffer error1");
} else {
logger::info(TAG, "AMediaCodec_queueInputBuffer success");
}
} else {
const auto status = AMediaCodec_queueInputBuffer(codec, index, 0, 0, encoder->getMicroseconds() / 1000, AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM);
if (status != AMEDIA_OK){
logger::error(TAG, "AMediaCodec_queueInputBuffer error2");
}
}
},
.onAsyncOutputAvailable = [](AMediaCodec *codec, void *userdata, int32_t index, AMediaCodecBufferInfo *bufferInfo) -> void {
size_t outSize = 0;
const auto buffer = AMediaCodec_getOutputBuffer(codec, index, &outSize);
const auto encoder = reinterpret_cast(userdata);
encoder->h264File->write(reinterpret_cast(buffer), outSize);
const auto status = AMediaCodec_releaseOutputBuffer(codec, index, false);
logger::info(TAG, "index: %d, bufferInfo->size: %d, outSize: %d, threadId: %d", index, bufferInfo->size, outSize, std::this_thread::get_id());
if (status != AMEDIA_OK){
logger::error(TAG, "onAsyncOutputAvailable AMediaCodec_queueInputBuffer error1");
}
if (bufferInfo->flags == AMEDIACODEC_BUFFER_FLAG_END_OF_STREAM){
logger::info(TAG, "onAsyncOutputAvailable end...");
}
},
.onAsyncFormatChanged = [](AMediaCodec *codec, void *userdata, AMediaFormat *format) -> void {
logger::info(TAG, "onAsyncFormatChanged...");
},
.onAsyncError =  [](AMediaCodec *codec, void *userdata, media_status_t error,int32_t actionCode, const char *detail) ->  void {
logger::error(TAG, "onAsyncError media_status_t: %d, actionCode: %d, detail: %s", error, actionCode, detail);
},
};
// 注册回调

logger::info(TAG, "android api: %d, runtime api: %d", __ANDROID_API__, android_get_device_api_level());
if (android_get_device_api_level() >= __ANDROID_API_P__){
pSetAsyncNotifyCallback(mediaCodec, callback, this);
} else {
throw std::runtime_error("__ANDROID_API__ < 28");
}
// #if __ANDROID_API__ >= __ANDROID_API_P__
//         AMediaCodec_setAsyncNotifyCallback(mediaCodec, callback, this);
// #else
//         throw std::runtime_error("__ANDROID_API__ < 28");
// #endif

status = AMediaCodec_start(mediaCodec);
if (status != AMEDIA_OK){
throw std::runtime_error("start error");
} else {
logger::info(TAG, "start success...");
}
}
Encoder::~Encoder() {
logger::info(TAG, "析构...");
if (this->mediaCodec != nullptr){
AMediaCodec_stop(mediaCodec);
AMediaCodec_delete(mediaCodec);
this->mediaCodec = nullptr;
}
if (mediaFormat != nullptr){
AMediaFormat_delete(mediaFormat);
this->mediaFormat = nullptr;
}
if(this->yuvFile != nullptr){
this->yuvFile->close();
this->yuvFile = nullptr;
}
if(this->h264File != nullptr){
this->h264File->close();
this->h264File = nullptr;
}
if (this->libHandle != nullptr){
dlclose(this->libHandle);
this->libHandle = nullptr;
}
}
}

extern "C"
JNIEXPORT jlong JNICALL
Java_io_github_okhttplearn_utils_Utils_nativeEncoder(JNIEnv *env, jobject thiz) {
learn01::Encoder* encoder = nullptr;
try {
encoder = new learn01::Encoder();
} catch (const std::exception &e){
delete encoder;
encoder = nullptr;
env->ThrowNew(env->FindClass("java/lang/RuntimeException"), e.what());
}
return reinterpret_cast(encoder);
}

extern "C"
JNIEXPORT void JNICALL
Java_io_github_okhttplearn_utils_Utils_nativeReleaseEncoder(JNIEnv *env, jobject thiz, jlong ptr) {
const auto* const encoder = reinterpret_cast(ptr);
delete encoder;
}
Quellcode
OkhttpLearn
log

Code: Select all

2025-11-23 21:12:20.756  1731-2096  learn001                io.github.okhttplearn                I  onAsyncInputAvailable outSize: 1382400, frameSize: 1382400, gCount: 1382400, threadId: -804506688, timestamp: 1763903540756
Beschreibung
Blockiert auf AMediaCodec_queueInputBuffer(codec, index, 0, FrameSize, Encoder->getMicroseconds() / 1000, 0);
Kotlin ist kein Problem

Code: Select all

//  ffplay -x 1280 -y 720 -f rawvideo -pixel_format yuv420p -video_size 3840x2176 -framerate 60 output1.yuv
internal suspend fun yuvToh264(context: Context, yuvUri: Uri, h264Uri: Uri): Unit = suspendCancellableCoroutine{ continuation ->

val mediaCodecList = MediaCodecList(MediaCodecList.ALL_CODECS)

// 视频解码器材
val mkvDecoders: List = mediaCodecList.codecInfos
.filter { !it.isEncoder && MediaFormat.MIMETYPE_VIDEO_HEVC in it.supportedTypes }

if (mkvDecoders.isEmpty()){
Log.i(TAG, "videoToYuvPcm -> 不支持mkv解码")
if (continuation.isActive){
continuation.resume(Unit)
}
return@suspendCancellableCoroutine
}

// 拿解码器
val mkvDecoder: MediaCodecInfo = mkvDecoders.firstOrNull {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
it.isHardwareAccelerated
} else {
true
}
} ?: mkvDecoders.first()

// 重试解码器 android 8 不支持  hevc (Main 10)
// val mkvDecoder: MediaCodecInfo = mkvDecoders[1]
// MediaCodec.createByCodecName(h264Decoder.name)
Log.i(TAG, "videoToYuvPcm1 ->  mkvDecoderName: ${mkvDecoder.name}")

mkvDecoders.forEach { mediaCodecInfo ->
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
Log.i(TAG, "mkvDecoders -> name: ${mediaCodecInfo.name}, canonicalName: ${mediaCodecInfo.canonicalName}, isAlias: ${mediaCodecInfo.isAlias}, isVendor: ${mediaCodecInfo.isVendor}, isHardwareAccelerated: ${mediaCodecInfo.isHardwareAccelerated}, isEncoder: ${mediaCodecInfo.isEncoder}, isSoftwareOnly: ${mediaCodecInfo.isSoftwareOnly}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
} else {
Log.i(TAG, "mkvDecoders -> name: ${mediaCodecInfo.name}, isEncoder: ${mediaCodecInfo.isEncoder}, supportedTypes: ${mediaCodecInfo.supportedTypes.joinToString()}")
}
mediaCodecInfo.supportedTypes.forEach { mimeType: String ->
if (mimeType.lowercase() == MediaFormat.MIMETYPE_VIDEO_HEVC){
val mediaCodecInfoCodecCapabilities: MediaCodecInfo.CodecCapabilities = mediaCodecInfo.getCapabilitiesForType(mimeType)
mediaCodecInfoCodecCapabilities.profileLevels.forEach { codecProfileLevel ->
if (codecProfileLevel.profile == MediaCodecInfo.CodecProfileLevel.HEVCProfileMain){
if (codecProfileLevel.level >= MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel62){
Log.i(TAG, "h265ToYuvPcm -> 支持 8k h265 name: ${mediaCodecInfo.name}")
} else {
Log.i(TAG, "h265ToYuvPcm -> 不支持 8k h265 name: ${mediaCodecInfo.name}")
}
}
}
}
}
}

val width = 672      /* 662 */
val height = 1280

val frameRate = 60
val frameSize = width * height * 3 / 2

val mediaFormat: MediaFormat = MediaFormat.createVideoFormat(MediaFormat.MIMETYPE_VIDEO_AVC, width, height).apply {
setInteger(MediaFormat.KEY_FRAME_RATE, frameRate)
setInteger(MediaFormat.KEY_BIT_RATE, 2_000_000) // 可根据分辨率调整
setInteger(MediaFormat.KEY_COLOR_FORMAT, MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible)
setInteger(MediaFormat.KEY_I_FRAME_INTERVAL, 1)
}

val mediaCodec: MediaCodec = MediaCodec.createEncoderByType(MediaFormat.MIMETYPE_VIDEO_AVC)
mediaCodec.configure(mediaFormat, null, null, MediaCodec.CONFIGURE_FLAG_ENCODE)

val yuvInputStream: InputStream = context.contentResolver.openInputStream(yuvUri)!!
val h264OutputStream: OutputStream = context.contentResolver.openOutputStream(h264Uri)!!
val bytes = ByteArray(frameSize)

mediaCodec.setCallback(object : MediaCodec.Callback() {
override fun onError(
codec: MediaCodec,
e: MediaCodec.CodecException
) {
Log.e(
TAG,
"onError name: ${codec.name}, thread: ${Thread.currentThread()}, error: ${e.message}",
e
)
}

override fun onInputBufferAvailable(
codec: MediaCodec,
index: Int
) {
Log.i(
TAG,
"onInputBufferAvailable -> name: ${mediaCodec.name}, index: $index, thread: ${Thread.currentThread()}"
)
val inputBuffer: ByteBuffer = codec.getInputBuffer(index) ?: return
val size: Int = yuvInputStream.read(bytes, 0, frameSize)
if (size == frameSize) {
inputBuffer.put(bytes, 0, size)
codec.queueInputBuffer(index, 0, size, System.nanoTime() / 1000, 0)
} else {
codec.queueInputBuffer(
index,
0,
0,
System.nanoTime() / 1000,
MediaCodec.BUFFER_FLAG_END_OF_STREAM
)
}
}

override fun onOutputBufferAvailable(
codec: MediaCodec,
index: Int,
info: MediaCodec.BufferInfo
) {
Log.i(
TAG,
"onOutputBufferAvailable ->  name: ${codec.name}, index: $index, info: ${info.size}, thread: ${Thread.currentThread()}"
)

val outputBuffer: ByteBuffer = codec.getOutputBuffer(index) ?: return

outputBuffer.get(bytes, 0, info.size)
h264OutputStream.write(bytes, 0, info.size)

codec.releaseOutputBuffer(index, false)

if (info.flags == MediaCodec.BUFFER_FLAG_END_OF_STREAM) {
Log.i(TAG, "onOutputBufferAvailable -> == 编码结束...") // todo
yuvInputStream.close()
h264OutputStream.close()
if (continuation.isActive) {
Log.i(TAG, "pcmToAac -> 解码完成 resume before...")
continuation.resume(Unit)
Log.i(TAG, "pcmToAac -> 解码完成 resume after...")
}
}
}

override fun onOutputFormatChanged(
codec: MediaCodec,
format: MediaFormat
) {
Log.i(
TAG,
"onOutputFormatChanged -> name: ${codec.name}, format: $format"
)
}
})
Log.i(TAG, "pcmToAac -> before start...")
mediaCodec.start()
Log.i(TAG, "pcmToAac -> after start...")
}

/**
* yuv420p 转 nv21
*/
fun yuv420pToNv21(yuv420p: ByteArray, nv12: ByteArray, width: Int, height: Int) {

val frameSize: Int = width * height
val qFrameSize: Int = frameSize / 4
// Y 拷贝
System.arraycopy(yuv420p, 0, nv12, 0, frameSize)

val uStart: Int = frameSize
val vStart: Int = frameSize + qFrameSize
var uvIndex: Int = frameSize

for (i in 0 until qFrameSize) {
nv12[uvIndex++] = yuv420p[vStart + i] // V
nv12[uvIndex++] = yuv420p[uStart + i] // U
}
}

Quick Reply

Change Text Case: 
   
  • Similar Topics
    Replies
    Views
    Last post