AMediaCodec_queueInputBuffer заблокированC++

Программы на C++. Форум разработчиков
Ответить
Anonymous
 AMediaCodec_queueInputBuffer заблокирован

Сообщение Anonymous »

AMediaCodec_queueInputBuffer заблокирован
Заголовок
Android NDK Кодировщик MediaCodec AVC блокируется/перестает отвечать после первой отправки входного буфера в асинхронном режиме
Описание проблемы
Я реализую видеокодер H.264 с помощью Android NDK API MediaCodec C в режиме асинхронного обратного вызова (AMediaCodec_setAsyncNotifyCallback).
Я столкнулся с критической проблемой: после успешной отправки самого первого кадра данных внутри обратного вызова onAsyncInputAvailable как последующие входные обратные вызовы (onAsyncInputAvailable), так и все ожидаемые выходные обратные вызовы (onAsyncOutputAvailable) перестают запускаться. Затем программа зависает или перестает отвечать на запросы в течение длительного периода времени.
Мне нужны рекомендации по правильной реализации модели производитель-потребитель в асинхронном режиме NDK, чтобы предотвратить эту тупиковую блокировку и обеспечить надежную передачу сигналов EOS.
Заголовок C++
//
// 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++
//
// 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/demuxe ... ormat.html
* https://blogs.hdvr.top/2021/09/22/%E9%9 ... %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;
}

Исходный код
OkhttpLearn
log
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

Описание
Заблокировано на AMediaCodec_queueInputBuffer(codec, index, 0, FrameSize, encoder->getMicroсекунды() / 1000, 0);
С Kotlin нет проблем
// 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
}
}


Подробнее здесь: https://stackoverflow.com/questions/798 ... is-blocked
Ответить

Быстрый ответ

Изменение регистра текста: 
Смайлики
:) :( :oops: :roll: :wink: :muza: :clever: :sorry: :angel: :read: *x)
Ещё смайлики…
   
К этому ответу прикреплено по крайней мере одно вложение.

Если вы не хотите добавлять вложения, оставьте поля пустыми.

Максимально разрешённый размер вложения: 15 МБ.

Вернуться в «C++»