自定义AMediaDataSource调用AMediaExtractor_setDataSourceCustom报错求助
我之前也碰到过类似的坑,AMEDIA_ERROR_UNKNOWN这个错误在自定义AMediaDataSource时,大概率是回调函数的签名不匹配、实现逻辑有问题,或者初始化细节没做到位导致的。结合你的情况,咱们逐个排查:
1. 严格匹配回调函数的签名
这是最常见的问题,NDK对AMediaDataSource的回调函数签名有严格要求,哪怕返回值类型、参数顺序差一点,系统都无法正确调用这些回调,直接返回未知错误。你需要确保每个回调的签名完全符合NDK定义:
// getSize回调:返回媒体数据总大小 int64_t (*getSize)(void* userdata); // readAt回调:从offset位置读取size字节到buffer,返回实际读取字节数(0表示EOF,-1表示错误) ssize_t (*readAt)(void* userdata, off64_t offset, void* buffer, size_t size); // close回调:释放资源 void (*close)(void* userdata); // getAvailableSize回调:返回从offset开始可读取的字节数 int64_t (*getAvailableSize)(void* userdata, off64_t offset);
比如如果你把readAt的off64_t写成了int64_t,或者返回值用了int而不是ssize_t,都会导致回调无法被触发。
2. 确保getAvailableSize的实现合法
在API 29中,getAvailableSize是必填的回调,而且返回值不能是负数。如果你的数据源是完整的字节流,直接返回总大小 - offset即可;如果是流式数据源(比如网络流),返回当前可读取的字节数。如果这个函数返回非法值,会直接导致AMediaExtractor初始化失败。
3. 检查初始化流程的完整性
虽然你的代码里已经设置了所有回调,但要确认:
- 没有把关键回调(比如
getSize、readAt)设为nullptr AMediaDataSource_new()和AMediaExtractor_new()都成功返回了非空指针(建议添加空指针检查)
4. 排除模拟器兼容性问题
部分API 29的模拟器对NDK自定义AMediaDataSource的支持有bug,建议你用真实的Pixel 3设备测试一下,看看是否能正常工作。
5. 添加日志调试
哪怕你觉得回调没被调用,也建议在每个回调函数里添加Android日志,确认系统是否真的没有触发它们:
#include <android/log.h> #define LOG_TAG "MediaDataSource" int64_t mediaDataSourceGetSize(void* userdata) { __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "getSize called"); // 返回你的数据总大小 return YOUR_TOTAL_DATA_SIZE; }
如果日志里看不到这些回调的输出,说明签名肯定有问题;如果能看到,就检查回调的返回值是否合法。
完整的正确配置示例
这里给你一个标准的初始化示例,你可以对照调整:
#include <media/NdkMediaExtractor.h> #include <android/log.h> #define LOG_TAG "MediaExtractorDemo" #define LOGD(...) __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, __VA_ARGS__) #define LOGE(...) __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, __VA_ARGS__) // 自定义数据(如果需要传递给回调,可以在这里定义) typedef struct { // 比如你的自定义字节流指针、总大小等 uint8_t* data; int64_t totalSize; } CustomData; int64_t mediaDataSourceGetSize(void* userdata) { CustomData* data = (CustomData*)userdata; LOGD("getSize called, totalSize: %lld", data->totalSize); return data->totalSize; } ssize_t mediaDataSourceReadAt(void* userdata, off64_t offset, void* buffer, size_t size) { CustomData* data = (CustomData*)userdata; LOGD("readAt called, offset: %lld, size: %zu", offset, size); if (offset >= data->totalSize) { return 0; // EOF } size_t readSize = size; if (offset + readSize > data->totalSize) { readSize = data->totalSize - offset; } memcpy(buffer, data->data + offset, readSize); return readSize; } void mediaDataSourceClose(void* userdata) { CustomData* data = (CustomData*)userdata; LOGD("close called"); // 释放自定义数据资源 free(data->data); free(data); } int64_t mediaDataSourceGetAvailableSize(void* userdata, off64_t offset) { CustomData* data = (CustomData*)userdata; LOGD("getAvailableSize called, offset: %lld", offset); if (offset >= data->totalSize) { return 0; } return data->totalSize - offset; } void initExtractor() { AMediaExtractor *ex = AMediaExtractor_new(); if (!ex) { LOGE("Failed to create AMediaExtractor"); return; } // 初始化自定义数据 CustomData* customData = (CustomData*)malloc(sizeof(CustomData)); customData->totalSize = YOUR_DATA_SIZE; customData->data = (uint8_t*)malloc(YOUR_DATA_SIZE); // 填充你的自定义字节流到customData->data AMediaDataSource* mediaSource = AMediaDataSource_new(); if (!mediaSource) { LOGE("Failed to create AMediaDataSource"); AMediaExtractor_delete(ex); free(customData->data); free(customData); return; } AMediaDataSource_setGetSize(mediaSource, mediaDataSourceGetSize); AMediaDataSource_setReadAt(mediaSource, mediaDataSourceReadAt); AMediaDataSource_setUserdata(mediaSource, customData); AMediaDataSource_setClose(mediaSource, mediaDataSourceClose); AMediaDataSource_setGetAvailableSize(mediaSource, mediaDataSourceGetAvailableSize); media_status_t status = AMediaExtractor_setDataSourceCustom(ex, mediaSource); if (status != AMEDIA_OK) { LOGE("Failed to set custom data source: %d", status); AMediaDataSource_delete(mediaSource); AMediaExtractor_delete(ex); free(customData->data); free(customData); return; } // 后续操作:获取轨道、配置MediaCodec等 int trackCount = AMediaExtractor_getTrackCount(ex); LOGD("Track count: %d", trackCount); // 使用完毕后记得释放资源 AMediaExtractor_delete(ex); // AMediaDataSource会被AMediaExtractor自动释放,不需要手动调用AMediaDataSource_delete }
总结
优先检查回调函数的签名是否完全匹配NDK的定义,这是最容易出错的点;其次确保每个回调的返回值合法,尤其是getAvailableSize;最后用真实设备验证排除模拟器的问题。
内容的提问来源于stack exchange,提问作者Nitenq

