نقل FFmpeg إلى Android

Home PDF

رابط النص الأصلي (CSDN)


البداية

بالنسبة للطلاب الذين لم يتعرفوا بعد على FFmpeg، يمكنهم أولاً الاطلاع على الأوامر الأساسية الشائعة لـ FFmpeg، والبحث بعمق في وظائفه – مثل دمج الصوت والفيديو، تشغيل أنواع مختلفة من الترميز، قص الفيديو، دمج عدة صور في فيديو وخلط الصوت، تحويل التنسيقات، وغيرها. يمكن أن توفر FFmpeg وظائف قوية ومثيرة للاهتمام للعديد من سيناريوهات التطبيق.

باستخدام تطبيقات مثل “التعليق الصوتي” كمثال، فإن التعليق الصوتي ليس فقط ممتعًا للغاية، بل غالبًا ما يجعل المنتج أكثر جاذبية. نحن نخطط لتطوير وحدة تعليق صوتي، وبالتالي نظرنا إلى FFmpeg، وحاولنا نقله إلى منصة Android. استغرق الأمر حوالي 3 إلى 4 أيام، وجربنا عدة إصدارات، ولم تنجح العديد من المقالات على الإنترنت، حتى وجدنا برنامجًا تعليميًا بعنوان “استخدام ffmpeg على android واستدعاء الواجهات”، والذي أخيرًا ساعدنا في تحقيق النجاح. أظهرت الاختبارات العملية أن فقط مزيج FFmpeg 1.2 مع ndk-r9 يمكن نقله بنجاح.


الفكرة

FFmpeg 是一个用 C 语言编写的项目,其中包含一个 main() 函数。我们的目标是:

  1. استخدم Android NDK لتجميع libffmpeg.so.
  2. باستخدام هذه المكتبة، قم بتجميع وتعديل ملف ffmpeg.c، وقم بتغيير اسم الدالة الأصلية main() إلى video_merge(int argc, char **argv)، مما يسمح باستدعائها مباشرة من JNI لإجراء عمليات مثل دمج الفيديو.

على سبيل المثال، يمكن تحقيق تركيب الفيديو بطريقة مشابهة لما يلي (المقابل لسطر الأوامر ffmpeg -i src1 -i src2 -y output):

video_merge(5, argv); // حيث يتم محاكاة وسائط سطر الأوامر بواسطة argv

البيئة

قبل البدء، يُنصح بالاطلاع على بعض البرامج التعليمية ذات الصلة، وعند مواجهة أي مشاكل، يمكنك العودة إلى هذه المقالة للمقارنة، لتجنب اتخاذ مسارات طويلة وغير ضرورية.


تعديل الواجهة وملف Android.mk

عند كتابة واجهة JNI لـ FFmpeg، تحتاج إلى كتابة ملف Android.mk لربط المكتبات، وبالتالي إنشاء ملف .so قابل للاستخدام. بعض نماذج Android.mk قد لا تعمل مباشرة في بيئات مختلفة. وظيفتها هي إخبار NDK بأي ملفات المصدر التي تحتاج إلى الترجمة، وإلى أي مكتبة يجب الربط، وغيرها من المعلومات.

لقد اعتمدت أسلوبًا يتمثل في “التجميع مرتين، ثم الربط”:

  1. أولاً، قم بتجميع مكتبة مشتركة تُسمى myffmpeg.
  2. في وحدة أخرى تُسمى ffmpeg-jni، قم بربط myffmpeg داخلها، ثم قم بإنشاء ملف .so المطلوب في النهاية.

بالإضافة إلى ذلك، يجب وضع ملف libffmpeg.so الذي تم تجميعه في دليل jni لضمان العثور عليه أثناء عملية الربط.


تصحيح FFmpeg

عندما تعمل مع FFmpeg، قد تواجه مشاكل تتطلب منك تصحيح الأخطاء. إليك بعض الخطوات التي يمكنك اتباعها لتصحيح FFmpeg:

1. تفعيل وضع التصحيح (Debug Mode)

يمكنك تفعيل وضع التصحيح في FFmpec باستخدام الخيار -loglevel debug. هذا سيظهر معلومات تفصيلية عن العملية التي يقوم بها FFmpeg.

   ffmpeg -loglevel debug -i input.mp4 output.avi

2. استخدام -report لإنشاء تقرير

يمكنك استخدام الخيار -report لإنشاء تقرير يحتوي على معلومات مفصلة عن العملية. هذا التقرير يمكن أن يكون مفيدًا في تحديد المشاكل.

   ffmpeg -i input.mp4 -report output.avi

سيتم إنشاء ملف تقرير في نفس الدليل الذي يتم تنفيذ الأمر منه.

3. فحص ملفات السجل (Log Files)

إذا كنت تواجه مشاكل معينة، يمكنك فحص ملفات السجل التي يتم إنشاؤها بواسطة FFmpeg. هذه الملفات تحتوي على معلومات مفصلة عن الأخطاء والتحذيرات.

4. استخدام -v لزيادة مستوى التفاصيل

يمكنك استخدام الخيار -v لزيادة مستوى التفاصيل في المخرجات. على سبيل المثال، -v verbose أو -v debug.

   ffmpeg -v debug -i input.mp4 output.avi

5. التأكد من الإصدار والتكوين

أحيانًا تكون المشاكل مرتبطة بإصدار معين من FFmpeg أو التكوين الذي تم استخدامه لبناء FFmpeg. يمكنك التحقق من الإصدار والتكوين باستخدام الأمر التالي:

   ffmpeg -version

6. استخدام -benchmark لقياس الأداء

إذا كنت تواجه مشاكل في الأداء، يمكنك استخدام الخيار -benchmark لقياس الوقت الذي تستغرقه كل عملية.

   ffmpeg -benchmark -i input.mp4 output.avi

7. التأكد من توفر المكتبات المطلوبة

تأكد من أن جميع المكتبات المطلوبة متوفرة ومثبتة بشكل صحيح. يمكنك استخدام الأمر ffmpeg -buildconf للتحقق من التكوين والمكتبات المستخدمة.

8. استخدام -f لتحديد التنسيق

إذا كنت تواجه مشاكل في تحديد التنسيق، يمكنك استخدام الخيار -f لتحديد التنسيق يدويًا.

   ffmpeg -f mp4 -i input.mp4 output.avi

9. التأكد من صحة ملف الإدخال

أحيانًا تكون المشاكل مرتبطة بملف الإدخال نفسه. تأكد من أن ملف الإدخال صالح ولا يحتوي على أخطاء.

10. طلب المساعدة من المجتمع

إذا لم تتمكن من حل المشكلة بنفسك، يمكنك طلب المساعدة من مجتمع FFmpeg عبر منصات مثل Stack Overflow أو قوائم البريد الإلكتروني الخاصة بـ FFmpeg.

باتباع هذه الخطوات، يمكنك تصحيح معظم المشاكل التي تواجهها أثناء العمل مع FFmpeg.

بعد نقل FFmpeg، غالبًا ما نحتاج إلى تصحيح الأخطاء في طبقة C من أجل استدعاء الوظائف عبر JNI. إذا كان بإمكاننا رؤية سجلات الإخراج التفصيلية كما في سطر الأوامر، فسيكون من الأسهل تحديد المشكلات.

في Eclipse، يمكنك الضغط على مفتاح Ctrl والنقر على موقع استدعاء مشابه لـ av_log لتتبع تنفيذ الدالة av_log_default_callback الموجودة في ffmpeg/libavutil/log.c. هذه الدالة تستدعي __android_log_print الخاص بـ Android لطباعة الرسائل في Logcat. من خلال مراجعة هذه المخرجات، يمكنك معرفة الحالة الداخلية لـ FFmpeg، مما يساعد في استكشاف الأخطاء مثل فشل التوليف أو عدم دعم ترميز معين.

أحيانًا، قد يلقي FFmpeg استثناءً يتسبب في تعطل التطبيق. يمكن استخدام الأمر التالي لتحديد المشكلة:

adb shell logcat | ndk-stack -sym obj/local/armeabi

ملاحظة: الكود أعلاه يستخدم أوامر adb و ndk-stack لتحليل سجلات الأخطاء (logcat) في تطبيقات Android التي تستخدم NDK (Native Development Kit). لا يتم ترجمة الأوامر أو المسارات لأنها أسماء أوامر محددة في نظام Android.

إذا كان لدى FFmpeg الأصلي main() في النهاية exit(0)، يرجى تذكر تعليقها، وإلا سيؤدي ذلك إلى خروج التطبيق.

تسريب الذاكرة وحلول Service

تسريب الذاكرة (Memory Leak) هو مشكلة شائعة في تطوير التطبيقات، حيث يتم تخصيص جزء من الذاكرة ولكن لا يتم تحريره بعد الانتهاء من استخدامه. مع مرور الوقت، يمكن أن يؤدي ذلك إلى استهلاك كبير للذاكرة، مما يؤثر على أداء التطبيق وقد يتسبب في تعطيله.

في سياق تطبيقات Android، يمكن أن تحدث تسريبات الذاكرة عند استخدام Service بشكل غير صحيح. على سبيل المثال، إذا تم تشغيل Service ولم يتم إيقافه بشكل صحيح، فقد يستمر في استهلاك الذاكرة حتى بعد أن لا تكون هناك حاجة إليه.

حلول لتجنب تسريب الذاكرة مع Service

  1. استخدام IntentService بدلاً من Service:
    • IntentService هي فئة فرعية من Service تقوم تلقائيًا بإيقاف نفسها بعد الانتهاء من معالجة جميع الطلبات. هذا يقلل من خطر تسريب الذاكرة.
    public class MyIntentService extends IntentService {
        public MyIntentService() {
            super("MyIntentService");
        }
    
        @Override
        protected void onHandleIntent(Intent intent) {
            // تنفيذ المهام هنا
        }
    }
    
  2. إيقاف Service بشكل صحيح:
    • إذا كنت تستخدم Service مباشرة، تأكد من إيقافه بشكل صحيح بعد الانتهاء من استخدامه. يمكن القيام بذلك عن طريق استدعاء stopSelf() داخل Service.
    public class MyService extends Service {
        @Override
        public int onStartCommand(Intent intent, int flags, int startId) {
            // تنفيذ المهام هنا
            stopSelf(); // إيقاف الـ Service بعد الانتهاء
            return START_NOT_STICKY;
        }
    
        @Override
        public IBinder onBind(Intent intent) {
            return null;
        }
    }
    
  3. استخدام JobScheduler أو WorkManager:
    • بدلاً من استخدام Service، يمكنك استخدام JobScheduler أو WorkManager لإدارة المهام الخلفية. هذه الأدوات مصممة لإدارة الموارد بشكل أكثر كفاءة وتقليل خطر تسريب الذاكرة.
    // مثال باستخدام WorkManager
    WorkManager workManager = WorkManager.getInstance(context);
    workManager.enqueue(new OneTimeWorkRequest.Builder(MyWorker.class).build());
    
  4. مراقبة الذاكرة باستخدام أدوات مثل LeakCanary:
    • LeakCanary هي مكتبة تساعد في اكتشاف تسريبات الذاكرة في تطبيقات Android. يمكنك إضافتها إلى مشروعك لمراقبة الذاكرة واكتشاف التسريبات بسهولة.
    dependencies {
        debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.7'
    }
    

باتباع هذه الممارسات، يمكنك تقليل خطر تسريب الذاكرة في تطبيقات Android الخاصة بك، مما يؤدي إلى تحسين الأداء وتجربة المستخدم.

بعد اكتمال التوليف، إذا ظهر خطأ "INVALID HEAP ADDRESS IN dlfree ffmpeg" عند الاستدعاء مرة أخرى، فمن المرجح أن يكون ذلك بسبب عدم تحرير ذاكرة FFmpeg بشكل كامل. إحدى الحلول الوسطية هي وضع عملية التوليف في Service منفصل، وبعد اكتمال التوليف، يتم إنهاء هذا الـ Service لتنظيف الموارد.

<!-- AndroidManifest.xml -->
<service android:name=".FFmpegService" />

من خلال تسجيل Receiver وما شابه، وإنهاء الخدمة بشكل تلقائي بعد اكتمال التركيب، يمكن تجنب مشاكل الذاكرة الناتجة عن الاستدعاءات المتكررة.


المشكلات المحتملة

في التنفيذ الفعلي لتطبيقات دبلجة الصوت، تكون الممارسات الشائعة كالتالي:

  1. تحميل الفيديو الأصلي، والترجمات، وملف الصوت الذي تمت إزالة المقاطع التي تحتاج إلى دبلجة منه مسبقًا.
  2. تسجيل صوت المستخدم، وعند التوليف، يتم فقط دمج التسجيل مع المقاطع الصامتة.
  3. إذا لم تكن راضيًا عن وقت التوليف المحلي، يمكنك اختيار تحميل بيانات الصوت والفيديو إلى الخادم، حيث يتم التوليف على جانب الخادم، ثم يتم التنزيل بعد الانتهاء.

استخدام NDK في Eclipse

ليس من الضروري إدخال ndk-build في سطر الأوامر. يمكنك ببساطة النقر بزر الماوس الأيمن على المشروع في Eclipse، ثم اختيار Android Tools → Add Native Support، وبعد ذلك سيتم تنفيذ ndk-build تلقائيًا في كل مرة تضغط فيها على “Run”.

إنشاء ملفات رأس JNI بنقرة واحدة

كتابة ملفات رؤوس دوال JNI يمكن أن تكون عملية مرهقة، ولكن يمكن تسهيلها باستخدام الأمر javah الذي يقوم بإنشائها تلقائيًا.
في بيئة Eclipse، يمكنك تكوين الأمر كأداة خارجية واستخدام أمر مشابه لما يلي لإنشاء ملف الرأس:

javah -jni -classpath bin/classes -d jni com.example.ffmpeg.MyFFmpeg

بعد التنفيذ، سيتم إنشاء ملف مشابه لـ com_example_ffmpeg_MyFFmpeg.h في دليل jni. بعد ذلك، كل ما عليك فعله هو تضمينه (#include) في كود C الخاص بك وتنفيذ الوظائف المقابلة.


الخلاصة

يتطلب نقل FFmpeg إلى Android معرفة متعددة الجوانب، بما في ذلك تكوين بيئة NDK، وتجميع وربط C/C++، واستدعاء JNI، وتشفير وفك تشفير الصوت والفيديو، وغيرها. إذا واجهت مشاكل مثل عدم القدرة على التوليف، أو عدم دعم بعض التنسيقات، أو أخطاء في الربط، فأنت بحاجة إلى التحقق بعناية من التكوين وسجلات الإخراج. آمل أن تساعدك هذه المقالة في تجنب بعض المشاكل. إذا كنت تستخدم FFmpeg أيضًا، فلا تتردد في مشاركة تجاربك أو مشاكلك في قسم التعليقات، للتبادل والتعلم المتبادل.


Back 2025.01.18 Donate