הערות תכנות של OpenSL ES

אזהרה: OpenSL ES הוצאה משימוש. מפתחים צריכים להשתמש בספריית Oboe בקוד פתוח שזמינה ב-GitHub. Oboe הוא מעטפת של C++‎ שמספקת ממשק API שדומה מאוד ל-AAudio. אבוב קורא לאודיו כשהאודיו מופעל זמין, וחוזר ל-OpenSL ES אם אודיו לא זמין.

ההערות בקטע הזה משלבות את המפרט של OpenSL ES 1.0.1.

אובייקטים ואתחול ממשק

שני היבטים של מודל התכנות OpenSL ES שייתכן שמפתחים חדשים לא מכירים בין אובייקטים וממשקים לבין רצף האתחול.

בקצרה, אובייקט OpenSL ES דומה לקונספט האובייקט שפות תכנות כמו Java ו-C++, מלבד אובייקט OpenSL ES שניתן לצפייה רק דרך הממשקים המשויכים אליו. הוא כולל את הממשק הראשוני לכל האובייקטים, שנקרא SLObjectItf. אין כינוי לאובייקט עצמו, רק נקודת אחיזה לממשק SLObjectItf של האובייקט.

קודם יוצרים אובייקט OpenSL ES, שמחזיר SLObjectItf, ואז מפעילים אותו. הדבר דומה לדפוס התכנות הנפוץ של בניית המודל הראשון (שאף פעם לא ייכשל, למעט עקב מחסור בזיכרון או פרמטרים לא חוקיים), ואז השלמת האתחול (שעשוי להיכשל בגלל מחסור במשאבים). שלב המימוש מעניק להטמיע מקום הגיוני להקצאת משאבים נוספים במקרה הצורך.

כחלק מה-API ליצירת אובייקט, אפליקציה מציינת מערך של ממשקים רצויים שהוא מתכנן לרכוש מאוחר יותר. שימו לב שהמערך הזה לא לרכוש את הממשקים, הוא רק מצביע על כוונה עתידית להשיג אותם. הממשקים מאופיינים בתור משתמע או בוטה. יש לרשום במערך ממשק מפורש, אם הוא יושג מאוחר יותר. אין צורך לרשום ממשק משתמע יצירת מערך של אובייקט, אבל אין נזק לרשום אותם שם. ל-OpenSL ES יש עוד סוג של ממשק שנקרא דינמי, שלא צריך לציין באובייקט יוצר מערך ואפשר להוסיף אותו מאוחר יותר לאחר יצירת האובייקט. ההטמעה של Android מספקת תכונת נוחות להימנע מהמורכבות הזו, שמתוארת ממשקים דינמיים בזמן יצירת האובייקט.

אחרי יצירת האובייקט ומימושו, האפליקציה צריכה לרכוש ממשקים לכל אחד מהם לתכונה הדרושה, באמצעות GetInterface ב-SLObjectItf הראשוני.

לבסוף, האובייקט זמין לשימוש דרך הממשקים שלו, אבל שימו לב לאובייקטים מסוימים נדרש הגדרות נוספות. באופן ספציפי, נגן אודיו עם מקור נתונים מסוג URI צריך קצת יותר הכנה כדי לזהות שגיאות חיבור. לצפייה לפרטים נוספים, אפשר לעיין בקטע שליפה מראש של נגן אודיו.

אחרי שמסיימים עם האובייקט, צריך להשמיד אותו באופן מפורש. לראות את השמדת נתונים שבהמשך.

שליפה מראש של נגן אודיו

לנגן אודיו עם מקור נתונים של URI, Object::Realize מקצה אבל לא להתחבר למקור הנתונים (להכין) או להתחיל באחזור נתונים מראש. האירועים האלה מתרחשים אחרי שמצב השחקן מוגדר ל-SL_PLAYSTATE_PAUSED או ל-SL_PLAYSTATE_PLAYING.

יכול להיות שחלק מהמידע עדיין לא יהיה ידוע עד מאוחר יחסית ברצף הזה. לחשבון ספציפית, בשלב הראשון Player::GetDuration מחזירה SL_TIME_UNKNOWN הפונקציה MuteSolo::GetChannelCount מחזירה בהצלחה כאשר מספר הערוצים הוא אפס, או תוצאת השגיאה SL_RESULT_PRECONDITIONS_VIOLATED. ממשקי ה-API האלה מחזירים את הערכים הנכונים ברגע שנודעים.

מאפיינים אחרים שאינם ידועים בהתחלה כוללים את תדירות הדגימה סוג תוכן המדיה בפועל מבוסס על עיון בכותרת התוכן (בניגוד לכותרת של התוכן) סוג MIME שצוין על ידי האפליקציה, וגם סוג המאגר). הבעיות ייקבעו גם בהמשך במהלך להכין מראש/שליפה מראש, אבל אין ממשקי API לאחזר אותם.

הממשק של סטטוס השליפה מראש עוזר לזהות מתי כל המידע זמינה, או האפליקציה יכולה לבצע דגימה מעת לעת. שימו לב שמידע מסוים, כמו משך הזמן של סטרימינג MP3, ייתכן שלעולם לא יהיה ידוע.

ממשק הסטטוס של השליפה מראש (prefetch) שימושי גם לזיהוי שגיאות. רישום קריאה חוזרת (callback) ולהפעיל SL_PREFETCHEVENT_FILLLEVELCHANGE וSL_PREFETCHEVENT_STATUSCHANGE לפחות אירועים. אם שני האירועים האלה נשלחים בו-זמנית, וגם PrefetchStatus::GetFillLevel מדווח על רמת אפס, PrefetchStatus::GetPrefetchStatus דוחות SL_PREFETCHSTATUS_UNDERFLOW, אז זה מציין שגיאה במקור הנתונים שאי אפשר לשחזר. למשל, אי אפשר להתחבר למקור הנתונים כי שם הקובץ המקומי לא קיים או שה-URI של הרשת לא תקין.

בגרסה הבאה של OpenSL ES צפויה להתווסף תמיכה מפורשת יותר לטיפול בשגיאות במקור הנתונים. עם זאת, לצורך תאימות בינארית בעתיד, אנחנו מתכוונים להמשיך כדי לתמוך לדיווח על שגיאה שבעקבותיה אי אפשר לשחזר אותו.

לסיכום, רצף הקוד המומלץ הוא:

  1. Engine::CreateAudioPlayer
  2. Object:Realize
  3. Object::GetInterface למשך SL_IID_PREFETCHSTATUS
  4. PrefetchStatus::SetCallbackEventsMask
  5. PrefetchStatus::SetFillUpdatePeriod
  6. PrefetchStatus::RegisterCallback
  7. Object::GetInterface למשך SL_IID_PLAY
  8. Play::SetPlayState אל SL_PLAYSTATE_PAUSED, או SL_PLAYSTATE_PLAYING

הערה: ההכנה והשליפה מראש (prefetch) מתרחשות כאן. בפרק הזמן הזה תישמע התקשרות חזרה אל עדכוני סטטוס תקופתיים.

להרוס

חשוב להשמיד את כל האובייקטים כשיוצאים מהאפליקציה. יש להשמיד אובייקטים סדר הפוך של יצירתם, מכיוון שלא בטוח להשמיד אובייקט שיש לו אובייקטים. לדוגמה, צריך להרוס בסדר הזה: נגני אודיו ומכשירי הקלטה, מיקס פלט ולבסוף את המנוע.

ב-OpenSL ES אין תמיכה באיסוף אשפה אוטומטי הפניה וספירה של ממשקים. אחרי השיחה אל Object::Destroy, כל הפרטים הקיימים ממשקים שנגזר מהאובייקט המשויך הופך ללא מוגדר.

הטמעת Android OpenSL ES לא מזהה שימוש שגוי בממשקים כאלה. המשך השימוש בממשקים האלה לאחר השמדת האובייקט עלול לגרום לאפליקציה לקרוס או להתנהג בדרכים בלתי צפויות.

מומלץ להגדיר במפורש גם את ממשק האובייקטים הראשי וגם את כל מתממשק עם NULL כחלק מהרצף של השמדת אובייקטים, שמונע פעולה מקרית שימוש לרעה בכינוי לא פעיל בממשק.

הזזה סטריאו

אם משתמשים ב-Volume::EnableStereoPosition כדי להפעיל הזזת סטריאו של מקור מונו, יש הפחתה של 3 דציבלים בסך הכול עוצמת הצליל שלב. הדבר נדרש כדי שרמת עוצמת הקול הכוללת תישאר קבועה המקור הוא בוצעה הזזה מערוץ אחד לערוץ אחר. לכן, הפעל את מיקום הסטריאו רק אם יש צורך את זה. מידע נוסף זמין במאמר ב-Wikipedia בנושא הזזה של אודיו.

קריאות חוזרות (callbacks) ושרשורים

מטפלים בקריאות חוזרות (callback) נקראים בדרך כלל באופן סינכרוני כאשר ההטמעה מזהה אירוע. נקודה זו אסינכרונית ביחס לאפליקציה, לכן עליך להשתמש מנגנון סנכרון לשליטה בגישה לכל משתנה שמשותף בין האפליקציה שמטפל בקריאה חוזרת (callback). בקוד לדוגמה, למשל בתורי מאגר נתונים זמני, השמטנו את הערך הזה סנכרון או שימוש בסנכרון חסימה כדי לשמור על פשטות. עם זאת, פונקציות תקינות של סנכרון החסימה הוא קריטי לכל קוד ייצור.

הגורמים המטפלים בקריאות חוזרות (callback) מופעלים משרשורים פנימיים שאינם של אפליקציות, שלא מצורפים בסביבת זמן הריצה של Android, לא ניתן להשתמש ב-JNI. כי השרשורים הפנימיים האלה קריטי ל- התקינות של הטמעת OpenSL ES, גם handler של קריאה חוזרת לא צריך לחסום או לבצע עבודה מופרזת.

אם עליכם להשתמש ב-JNI או לבצע עבודה שלא פרופורציונלית לקריאה החוזרת, במקום זאת עליכם לשלוח אירוע לעיבוד על ידי שרשור אחר. דוגמאות של עומס עבודה קביל של קריאה חוזרת (callback) כולל עיבוד והעברה למאגר הנתונים הזמני הבא של הפלט (לנגן AudioPlayer), מעבד את מאגר הנתונים הזמני למילוי אוטומטי ומעביר את הסרטון הבא לתור מאגר נתונים זמני ריק (למכשיר AudioRecorder) או ממשקי API פשוטים, כמו רוב משפחת Get. לצפייה הקטע ביצועים שבהמשך לגבי עומס העבודה.

שימו לב שההיפך הוא בטוח: שרשור של אפליקציה ל-Android שנכנסו ל-JNI מורשה: לקרוא ישירות לממשקי API של OpenSL ES, כולל אלה שחוסמים. עם זאת, לא מומלץ לחסום קריאות מה-thread הראשי, כי הן עלולות לגרום לApplication Not Responding (ANR).

הקביעה לגבי ה-thread שקורא ל-handler של קריאה חוזרת נמצאת ברובה עד יישום בפועל. הסיבה לגמישות הזו היא לאפשר אופטימיזציות עתידיות, במיוחד ב- ומכשירים עם מספר ליבות.

לא בטוח שה-thread שבו פועל ה-handler של קריאה חוזרת יהיה זהה בכל קריאות שונות. לכן אין להסתמך על ה-pthread_t שהוחזר על ידי pthread_self() או pid_t שהוחזרו על ידי gettid() כדי באופן עקבי בכל סוגי השיחות. מאותה סיבה, אל תשתמשו בממשקי API של אחסון מקומי של שרשור (TLS), כמו pthread_setspecific() ו-pthread_getspecific() משיחה חוזרת.

ההטמעה מבטיחה קריאות חוזרות (callback) בו-זמניות מאותו סוג, עבור אותו אובייקט, לא יתרחשו. עם זאת, אפשר לבצע בו-זמנית קריאות חוזרות (callback) מסוגים שונים לאותו אובייקט שרשורים שונים.

ביצועים

OpenSL ES הוא ממשק API מקורי של C, ולכן לשרשורים של אפליקציות שאינן בזמן ריצה שנקראות OpenSL ES אין תקורה שקשורה לזמן ריצה, למשל השהיות לאיסוף אשפה. חריג אחד שמתואר בהמשך, אין יתרון ביצועים נוסף לשימוש ב-OpenSL ES מלבד זה. באופן ספציפי, השימוש ב-OpenSL ES לא מבטיח שיפורים כמו זמן אחזור אודיו קצר יותר ותעדוף תזמון גבוה יותר מזה שהפלטפורמה מספקת בדרך כלל. לעומת זאת, מכיוון שהמודל פלטפורמת Android והטמעות ספציפיות של מכשירים ממשיכים להתפתח, אפליקציית OpenSL ES יכולים להפיק תועלת משיפורים עתידיים בביצועי המערכת.

אחת מהשיפורים האלה היא תמיכה בזמן אחזור קצר יותר של פלט האודיו. היסודות להפחתת זמן האחזור של הפלט נכללו לראשונה ב-Android 4.1 (API ברמה 16), ולאחר מכן התקדמות נוספת התרחשה ב-Android 4.2 (API ברמה 17). שיפורים אלה זמינים דרך OpenSL ES להטמעות מכשירים לתבוע בעלות על התכונה android.hardware.audio.low_latency. אם המכשיר לא משתמש בתכונה הזו אבל תומך ב-Android 2.3 (רמת API 9) ואילך, עדיין תוכלו להשתמש בממשקי OpenSL ES API, אבל זמן האחזור של הפלט עשוי להיות ארוך יותר. נמוכה יותר נעשה שימוש בנתיב זמן האחזור של הפלט רק אם האפליקציה מבקשת גודל מאגר נתונים זמני וקצב דגימה שהם תואם לתצורת הפלט המקורי של המכשיר. הפרמטרים האלה ספציפיות למכשיר, הוא כפי שמתואר בהמשך.

החל מגרסה 4.2 של Android‏ (רמת API 17), אפליקציה יכולה לשלוח שאילתה לגבי קצב הדגימה הטבעי או האופטימלי של הפלט וגודלו של מאגר הנתונים (buffer) של מקור הפלט הראשי של המכשיר. בשילוב עם בדיקת התכונה שצוינה, אפליקציה יכולה עכשיו להגדיר את עצמה בהתאם לפלט זמן אחזור קצר במכשירים שתומכים בפלט.

עבור Android 4.2 (רמת API 17) ומטה, המספר הזמני של מאגר הנתונים הזמני או יותר הוא שנדרש לזמן אחזור קצר. החל מ-Android 4.3 (רמת API 18), מאגר נתונים זמני מספר אחד מספיק כדי לקצר את זמן האחזור.

כל ממשקי OpenSL ES לאפקטים של פלט מונעים את נתיב זמן האחזור הנמוך יותר.

הרצף המומלץ הוא:

  1. צריך לבדוק אם יש API ברמה 9 ואילך כדי לאשר את השימוש ב-OpenSL ES.
  2. מחפשים את התכונה android.hardware.audio.low_latency באמצעות קוד כמו:

    Kotlin

    import android.content.pm.PackageManager
    ...
    val pm: PackageManager = context.packageManager
    val claimsFeature: Boolean = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY)
    

    Java

    import android.content.pm.PackageManager;
    ...
    PackageManager pm = getContext().getPackageManager();
    boolean claimsFeature = pm.hasSystemFeature(PackageManager.FEATURE_AUDIO_LOW_LATENCY);
    
  3. צריך לבדוק אם רמת ה-API היא 17 ומעלה כדי לאשר את השימוש android.media.AudioManager.getProperty()
  4. קבלת קצב הדגימה וגודל מאגר הנתונים הזמני של הפלט המקורי או האופטימלי למכשיר הזה פלט ראשי זרם באמצעות קוד כמו:

    Kotlin

    import android.media.AudioManager
    ...
    val am = getSystemService(Context.AUDIO_SERVICE) as AudioManager
    val sampleRate: String = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE)
    val framesPerBuffer: String = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER)
    

    Java

    import android.media.AudioManager;
    ...
    AudioManager am = (AudioManager) getSystemService(Context.AUDIO_SERVICE);
    String sampleRate = am.getProperty(AudioManager.PROPERTY_OUTPUT_SAMPLE_RATE);
    String framesPerBuffer = am.getProperty(AudioManager.PROPERTY_OUTPUT_FRAMES_PER_BUFFER);
    
    שימו לב ש-sampleRate ו-framesPerBuffer הן מחרוזות. בדיקה ראשונה של null ואז המרה ל-int באמצעות Integer.parseInt().
  5. עכשיו משתמשים ב-OpenSL ES כדי ליצור AudioPlayer עם מאתר נתונים של תור מאגר PCM.

הערה: אפשר להשתמש הגודל של מאגר האודיו הזמני אפליקציית בדיקה לקביעת הגודל של מאגר הנתונים הזמני וקצב הדגימה של אודיו OpenSL ES יישומים במכשיר האודיו שלך. אפשר גם להיכנס ל-GitHub כדי לראות את דוגמאות לשטח אחסון זמני של אודיו.

מספר נגני האודיו עם זמן אחזור קצר הוא מוגבל. אם הבקשה מחייבת מכמה מקורות אודיו, כדאי לשלב את האודיו ברמת האפליקציה. חשוב להשמיד את האודיו שלכם השחקנים כשהפעילות שלכם מושהית, כי הם משאב גלובלי שמשותף עם אפליקציות אחרות.

כדי להימנע מתקלות נשמעות, ה-handler של הקריאה החוזרת בתור מאגר הנתונים הזמני חייב לפעול בטווח בחלון הזמן הצפוי. זה בדרך כלל לא מעיד על חסימה בלתי מוגבלת של מושתקים, תנאים או פעולות קלט/פלט (I/O). במקום זאת, כדאי לנסות נעילות, לנעול ולהמתין עם זמנים קצובים לתפוגה, וגם אלגוריתמים שאינם חוסמים.

החישוב הנדרש כדי לעבד את מאגר הנתונים הזמני הבא (בשביל AudioPlayer) או לצרוך את השימוש הקודם מאגר הנתונים הזמני (ב-AudioRecord) צריך להימשך בערך אותו משך זמן בכל קריאה חוזרת. נמנעים מאלגוריתמים שפועלים במשך זמן לא קבוע או שגרים את החישובים שלהם. החישוב של הקריאה החוזרת (callback) מתפוצץ אם זמן המעבד (CPU) עבר בקריאה חוזרת כלשהי גדול באופן משמעותי מהממוצע. לסיכום, זמן הביצוע האידאלי הוא השונות של ה-handler תהיה קרובה לאפס, וה-handler לא יחסום אותו למשך זמנים בלתי מוגבלים.

אפשר לקצר את זמן האחזור של האודיו רק בפלטים האלה:

במכשירים מסוימים, זמן האחזור של הרמקול גבוה יותר מזה של נתיבים אחרים בגלל עיבוד אותות דיגיטלי של תיקון והגנה על הרמקול.

החל מ-Android 5.0 (רמת API 21), זמן אחזור קצר יותר קלט אודיו נתמך במכשירים נבחרים. כדי ליהנות מהיתרונות של התכונה הזו, צריך קודם לאשר שפלט של זמן אחזור קצר זמין כמו שמתואר למעלה. היכולת לקצר את זמן האחזור הוא דרישה מוקדמת לתכונת הקלט עם זמן אחזור קצר. לאחר מכן, יוצרים מקליט אודיו עם אותו את שיעור הדגימה ואת הגודל של מאגר הנתונים הזמני שישמשו לפלט. ממשקי OpenSL ES לאפקטים של קלט את הנתיב של זמן האחזור הנמוך יותר. ההגדרה הקבועה מראש של ההקלטה צריך להשתמש ב-SL_ANDROID_RECORDING_PRESET_VOICE_RECOGNITION לזמן אחזור נמוך יותר. הזה ההגדרה משביתה מראש את עיבוד האותות הדיגיטליים שספציפיים למכשיר ועשויה להוסיף זמן אחזור לנתיב הקלט. למידע נוסף על הגדרות קבועות מראש להקלטה, הגדרות Android הממשק שלמעלה.

לקלט ולפלט בו-זמנית, משתמשים ב-handlers נפרדים להשלמת תור של מאגר נתונים זמני צד שלישי. אין ערובה לסדר היחסי של קריאות החזרה האלה, או לסנכרון של שעוני האודיו, גם אם בשני הצדדים נעשה שימוש באותה קצב דגימה. האפליקציה שלך צריך לאחסן במאגר נתונים זמני עם סנכרון תקין של מאגר נתונים זמני.

אחת התוצאות של שעוני אודיו שעשויים להיות עצמאיים היא הצורך בקצב דגימה אסינכרוני להמרה. שיטה פשוטה (אבל לא אידיאלית לאיכות האודיו) להמרת תדירות דגימה אסינכרונית היא שכפול או השמטה של דגימות לפי הצורך ליד נקודת מעבר לאפס. מתוחכם יותר היא אפשרית.

מצבי ביצועים

החל מ-Android 7.1 (API ברמה 25), ב-OpenSL ES הוספנו דרך לציין מצב ביצועים של נתיב האודיו. האפשרויות הן:

  • SL_ANDROID_PERFORMANCE_NONE: אין דרישה ספציפית לביצועים. הפעלת אפקטים של חומרה ותוכנה.
  • SL_ANDROID_PERFORMANCE_LATENCY: העדיפות ניתנת לזמן האחזור. ללא חומרה או את האפקטים של התוכנה. זהו מצב ברירת המחדל.
  • SL_ANDROID_PERFORMANCE_LATENCY_EFFECTS: עדיפות לזמן אחזור ועדיין מאפשרים אפקטים של חומרה ותוכנה.
  • SL_ANDROID_PERFORMANCE_POWER_SAVING: עדיפות שניתנה לשימור אנרגיה. הפעלת אפקטים של חומרה ותוכנה.

הערה: אם אין לכם צורך בנתיב זמן אחזור קצר ואתם רוצים להשתמש בו את היתרון של אפקטים קוליים מובנים במכשיר (לדוגמה, כדי לשפר את האקוסטיקה איכות להפעלת וידאו), עליך להגדיר באופן מפורש את מצב הביצועים כ- SL_ANDROID_PERFORMANCE_NONE.

כדי להגדיר את מצב הביצועים, צריך להפעיל את SetConfiguration באמצעות Android ממשק תצורה, כפי שמוצג בהמשך:

  // Obtain the Android configuration interface using a previously configured SLObjectItf.
  SLAndroidConfigurationItf configItf = nullptr;
  (*objItf)->GetInterface(objItf, SL_IID_ANDROIDCONFIGURATION, &configItf);

  // Set the performance mode.
  SLuint32 performanceMode = SL_ANDROID_PERFORMANCE_NONE;
    result = (*configItf)->SetConfiguration(configItf, SL_ANDROID_KEY_PERFORMANCE_MODE,
                                                     &performanceMode, sizeof(performanceMode));

אבטחה והרשאות

מבחינת מי שיכול לעשות מה, האבטחה ב-Android מתבצעת ברמת התהליך. תכנות Java קוד שפה לא יכול לעשות יותר מאשר קוד נייטיב, וגם קוד מקומי לא יכול לעשות יותר קוד של שפת התכנות Java. ההבדלים היחידים ביניהם הם ממשקי ה-API הזמינים.

אפליקציות שמשתמשות ב-OpenSL ES חייבות לבקש את ההרשאות שהן צריכות כדי ליצור אפליקציות דומות של ממשקי API לא מקוריים. לדוגמה, אם האפליקציה מקליטה אודיו, היא צריכה ההרשאה android.permission.RECORD_AUDIO. לאפליקציות שמשתמשות באפקטים קוליים נדרשות android.permission.MODIFY_AUDIO_SETTINGS אפליקציות שמפעילות משאבי URI ברשת צריכות את android.permission.NETWORK. מידע נוסף זמין במאמר הבא: עבודה עם המערכת הרשאות.

בהתאם לגרסה ולהטמעה של הפלטפורמה, כלים לניתוח תוכן מדיה וכן יכול להיות שרכיבי קודק התוכנה פועלות בהקשר של אפליקציית Android שקוראת ל-OpenSL ES (קודקי חומרה מופשטים אבל תלויים במכשיר). תוכן בפורמט שגוי שנועד לנצל את המנתח וקודק נקודות חולשה הן וקטור תוקף ידוע. מומלץ להפעיל מדיה רק ממקורות מהימנים מקורות אחרים של האפליקציה, או תחלק את האפליקציה למחיצות, כך שהקוד שמטפל במדיה מקורות לא מהימנים פועלים בסביבה של ארגז חול יחסית. לדוגמה, אפשר: לעבד מדיה ממקורות לא מהימנים בתהליך נפרד. למרות ששני התהליכים עדיין פועלות באותו UID, אבל ההפרדה הזו מקשה על התקפה.